Compare commits

..

1 Commits

Author SHA1 Message Date
Ayke van Laethem 96187990e3 WIP mips 2023-02-19 20:54:41 +00:00
40 changed files with 380 additions and 909 deletions
+1 -1
View File
@@ -89,7 +89,7 @@ jobs:
run: make wasi-libc run: make wasi-libc
- name: Test TinyGo - name: Test TinyGo
shell: bash shell: bash
run: make test GOTESTFLAGS="-short" run: make test GOTESTFLAGS="-v -short"
- name: Build TinyGo release tarball - name: Build TinyGo release tarball
run: make release -j3 run: make release -j3
- name: Test stdlib packages - name: Test stdlib packages
+1 -1
View File
@@ -99,7 +99,7 @@ jobs:
scoop install wasmtime scoop install wasmtime
- name: Test TinyGo - name: Test TinyGo
shell: bash shell: bash
run: make test GOTESTFLAGS="-short" run: make test GOTESTFLAGS="-v -short"
- name: Build TinyGo release tarball - name: Build TinyGo release tarball
shell: bash shell: bash
run: make build/release -j4 run: make build/release -j4
+1 -3
View File
@@ -30,7 +30,7 @@ GO ?= go
export GOROOT = $(shell $(GO) env GOROOT) export GOROOT = $(shell $(GO) env GOROOT)
# Flags to pass to go test. # Flags to pass to go test.
GOTESTFLAGS ?= GOTESTFLAGS ?= -v
# md5sum binary # md5sum binary
MD5SUM = md5sum MD5SUM = md5sum
@@ -474,8 +474,6 @@ smoketest:
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/pininterrupt $(TINYGO) build -size short -o test.hex -target=pca10040 examples/pininterrupt
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nano-rp2040 examples/rtcinterrupt
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/serial $(TINYGO) build -size short -o test.hex -target=pca10040 examples/serial
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/systick $(TINYGO) build -size short -o test.hex -target=pca10040 examples/systick
+81 -124
View File
@@ -169,14 +169,12 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
CodeModel: config.CodeModel(), CodeModel: config.CodeModel(),
RelocationModel: config.RelocationModel(), RelocationModel: config.RelocationModel(),
SizeLevel: sizeLevel, SizeLevel: sizeLevel,
TinyGoVersion: goenv.Version,
Scheduler: config.Scheduler(), Scheduler: config.Scheduler(),
AutomaticStackSize: config.AutomaticStackSize(), AutomaticStackSize: config.AutomaticStackSize(),
DefaultStackSize: config.StackSize(), DefaultStackSize: config.StackSize(),
NeedsStackObjects: config.NeedsStackObjects(), NeedsStackObjects: config.NeedsStackObjects(),
Debug: true, Debug: true,
LTO: config.LTO() != "legacy",
} }
// Load the target machine, which is the LLVM object that contains all // Load the target machine, which is the LLVM object that contains all
@@ -307,6 +305,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
actionID := packageAction{ actionID := packageAction{
ImportPath: pkg.ImportPath, ImportPath: pkg.ImportPath,
CompilerBuildID: string(compilerBuildID), CompilerBuildID: string(compilerBuildID),
TinyGoVersion: goenv.Version,
LLVMVersion: llvm.Version, LLVMVersion: llvm.Version,
Config: compilerConfig, Config: compilerConfig,
CFlags: pkg.CFlags, CFlags: pkg.CFlags,
@@ -453,24 +452,18 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
if err != nil { if err != nil {
return err return err
} }
if compilerConfig.LTO { if runtime.GOOS == "windows" {
buf := llvm.WriteThinLTOBitcodeToMemoryBuffer(mod) // 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() defer buf.Dispose()
_, err = f.Write(buf.Bytes()) _, err = f.Write(buf.Bytes())
} else { } else {
if runtime.GOOS == "windows" { // Otherwise, write bitcode directly to the file (probably
// Work around a problem on Windows. // faster).
// For some reason, WriteBitcodeToFile causes TinyGo to err = llvm.WriteBitcodeToFile(mod, f)
// 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 { if err != nil {
// WriteBitcodeToFile doesn't produce a useful error on its // WriteBitcodeToFile doesn't produce a useful error on its
@@ -518,11 +511,24 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
// Create runtime.initAll function that calls the runtime // Create runtime.initAll function that calls the runtime
// initializer of each package. // initializer of each package.
initAllModule := createInitAll(ctx, config, compilerConfig, lprogram.Sorted()) llvmInitFn := mod.NamedFunction("runtime.initAll")
err := llvm.LinkModules(mod, initAllModule) llvmInitFn.SetLinkage(llvm.InternalLinkage)
if err != nil { llvmInitFn.SetUnnamedAddr(true)
return fmt.Errorf("failed to link module: %w", err) transform.AddStandardAttributes(llvmInitFn, config)
llvmInitFn.Param(0).SetName("context")
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.GlobalValueType(), pkgInit, []llvm.Value{llvm.Undef(i8ptrType)}, "")
} }
irbuilder.CreateRetVoid()
// After linking, functions should (as far as possible) be set to // After linking, functions should (as far as possible) be set to
// private linkage or internal linkage. The compiler package marks // private linkage or internal linkage. The compiler package marks
@@ -555,7 +561,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
// Run all optimization passes, which are much more effective now // Run all optimization passes, which are much more effective now
// that the optimizer can see the whole program at once. // that the optimizer can see the whole program at once.
err = optimizeProgram(mod, config) err := optimizeProgram(mod, config)
if err != nil { if err != nil {
return err return err
} }
@@ -588,7 +594,12 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
defer llvmBuf.Dispose() defer llvmBuf.Dispose()
return result, os.WriteFile(outpath, llvmBuf.Bytes(), 0666) return result, os.WriteFile(outpath, llvmBuf.Bytes(), 0666)
case ".bc": case ".bc":
buf := llvm.WriteThinLTOBitcodeToMemoryBuffer(mod) var buf llvm.MemoryBuffer
if config.UseThinLTO() {
buf = llvm.WriteThinLTOBitcodeToMemoryBuffer(mod)
} else {
buf = llvm.WriteBitcodeToMemoryBuffer(mod)
}
defer buf.Dispose() defer buf.Dispose()
return result, os.WriteFile(outpath, buf.Bytes(), 0666) return result, os.WriteFile(outpath, buf.Bytes(), 0666)
case ".ll": case ".ll":
@@ -610,44 +621,23 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
dependencies: []*compileJob{programJob}, dependencies: []*compileJob{programJob},
result: objfile, result: objfile,
run: func(*compileJob) error { run: func(*compileJob) error {
llvmBuf := llvm.WriteThinLTOBitcodeToMemoryBuffer(mod) var llvmBuf llvm.MemoryBuffer
if config.UseThinLTO() {
llvmBuf = llvm.WriteThinLTOBitcodeToMemoryBuffer(mod)
} else {
var err error
llvmBuf, err = machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
if err != nil {
return err
}
}
defer llvmBuf.Dispose() defer llvmBuf.Dispose()
return os.WriteFile(objfile, llvmBuf.Bytes(), 0666) return os.WriteFile(objfile, llvmBuf.Bytes(), 0666)
}, },
} }
// Add job to create the runtime.initAll function in a new module.
initAllJob := &compileJob{
description: "create runtime.initAll",
result: filepath.Join(tmpdir, "runtime-initAll.bc"),
run: func(job *compileJob) (err error) {
// Create module with runtime.initAll.
ctx := llvm.NewContext()
defer ctx.Dispose()
initAllMod := createInitAll(ctx, config, compilerConfig, lprogram.Sorted())
defer initAllMod.Dispose()
// Write module to bitcode file.
llvmBuf := llvm.WriteThinLTOBitcodeToMemoryBuffer(initAllMod)
defer llvmBuf.Dispose()
return os.WriteFile(job.result, llvmBuf.Bytes(), 0666)
},
}
// Prepare link command. // Prepare link command.
var linkerDependencies []*compileJob linkerDependencies := []*compileJob{outputObjectFileJob}
switch config.LTO() {
case "legacy":
// Link all Go bitcode files together into a single large module and
// then do a ThinLTO link with the resulting large module + extra C
// files (from CGo etc).
linkerDependencies = append(linkerDependencies, outputObjectFileJob)
case "thin":
// Do a real thin link, with each Go package in a separate translation
// unit. This is faster than merging them into one big LTO module.
linkerDependencies = append(linkerDependencies, packageJobs...)
linkerDependencies = append(linkerDependencies, initAllJob)
}
result.Executable = filepath.Join(tmpdir, "main") result.Executable = filepath.Join(tmpdir, "main")
if config.GOOS() == "windows" { if config.GOOS() == "windows" {
result.Executable += ".exe" result.Executable += ".exe"
@@ -674,7 +664,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
job := &compileJob{ job := &compileJob{
description: "compile extra file " + path, description: "compile extra file " + path,
run: func(job *compileJob) error { run: func(job *compileJob) error {
result, err := compileAndCacheCFile(abspath, tmpdir, config.CFlags(), config.Options.PrintCommands) result, err := compileAndCacheCFile(abspath, tmpdir, config.CFlags(), config.UseThinLTO(), config.Options.PrintCommands)
job.result = result job.result = result
return err return err
}, },
@@ -692,7 +682,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
job := &compileJob{ job := &compileJob{
description: "compile CGo file " + abspath, description: "compile CGo file " + abspath,
run: func(job *compileJob) error { run: func(job *compileJob) error {
result, err := compileAndCacheCFile(abspath, tmpdir, pkg.CFlags, config.Options.PrintCommands) result, err := compileAndCacheCFile(abspath, tmpdir, pkg.CFlags, config.UseThinLTO(), config.Options.PrintCommands)
job.result = result job.result = result
return err return err
}, },
@@ -751,34 +741,36 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
} }
ldflags = append(ldflags, dependency.result) ldflags = append(ldflags, dependency.result)
} }
ldflags = append(ldflags, "-mllvm", "-mcpu="+config.CPU()) if config.UseThinLTO() {
if config.GOOS() == "windows" { ldflags = append(ldflags, "-mllvm", "-mcpu="+config.CPU())
// Options for the MinGW wrapper for the lld COFF linker. if config.GOOS() == "windows" {
ldflags = append(ldflags, // Options for the MinGW wrapper for the lld COFF linker.
"-Xlink=/opt:lldlto="+strconv.Itoa(optLevel), ldflags = append(ldflags,
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto")) "-Xlink=/opt:lldlto="+strconv.Itoa(optLevel),
} else if config.GOOS() == "darwin" { "--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"))
// Options for the ld64-compatible lld linker. } else if config.GOOS() == "darwin" {
ldflags = append(ldflags, // Options for the ld64-compatible lld linker.
"--lto-O"+strconv.Itoa(optLevel), ldflags = append(ldflags,
"-cache_path_lto", filepath.Join(cacheDir, "thinlto")) "--lto-O"+strconv.Itoa(optLevel),
} else { "-cache_path_lto", filepath.Join(cacheDir, "thinlto"))
// Options for the ELF linker. } else {
ldflags = append(ldflags, // Options for the ELF linker.
"--lto-O"+strconv.Itoa(optLevel), ldflags = append(ldflags,
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"), "--lto-O"+strconv.Itoa(optLevel),
) "--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"),
} )
if config.CodeModel() != "default" { }
ldflags = append(ldflags, if config.CodeModel() != "default" {
"-mllvm", "-code-model="+config.CodeModel()) ldflags = append(ldflags,
} "-mllvm", "-code-model="+config.CodeModel())
if sizeLevel >= 2 { }
// Workaround with roughly the same effect as if sizeLevel >= 2 {
// https://reviews.llvm.org/D119342. // Workaround with roughly the same effect as
// Can hopefully be removed in LLVM 15. // https://reviews.llvm.org/D119342.
ldflags = append(ldflags, // Can hopefully be removed in LLVM 15.
"-mllvm", "--rotation-max-header-size=0") ldflags = append(ldflags,
"-mllvm", "--rotation-max-header-size=0")
}
} }
if config.Options.PrintCommands != nil { if config.Options.PrintCommands != nil {
config.Options.PrintCommands(config.Target.Linker, ldflags...) config.Options.PrintCommands(config.Target.Linker, ldflags...)
@@ -1056,45 +1048,6 @@ func createEmbedObjectFile(data, hexSum, sourceFile, sourceDir, tmpdir string, c
return outfile.Name(), outfile.Close() return outfile.Name(), outfile.Close()
} }
// Create a module with the runtime.initAll function that calls all package
// initializers in initialization order.
func createInitAll(ctx llvm.Context, config *compileopts.Config, compilerConfig *compiler.Config, pkgs []*loader.Package) llvm.Module {
// Create LLVM module.
mod := ctx.NewModule("runtime-initAll")
// Add datalayout string.
machine, err := compiler.NewTargetMachine(compilerConfig)
if err != nil {
panic(err) // extremely unlikely to fail here (NewTargetMachine is called many times before)
}
defer machine.Dispose()
targetData := machine.CreateTargetData()
defer targetData.Dispose()
mod.SetTarget(config.Triple())
mod.SetDataLayout(targetData.String())
// Create empty runtime.initAll function.
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{i8ptrType}, false)
fn := llvm.AddFunction(mod, "runtime.initAll", fnType)
fn.SetUnnamedAddr(true)
transform.AddStandardAttributes(fn, config)
// Add calls to package initializers.
fn.Param(0).SetName("context")
block := mod.Context().AddBasicBlock(fn, "entry")
irbuilder := mod.Context().NewBuilder()
defer irbuilder.Dispose()
irbuilder.SetInsertPointAtEnd(block)
for _, pkg := range pkgs {
pkgInit := llvm.AddFunction(mod, pkg.Pkg.Path()+".init", fnType)
irbuilder.CreateCall(fnType, pkgInit, []llvm.Value{llvm.Undef(i8ptrType)}, "")
}
irbuilder.CreateRetVoid()
return mod
}
// optimizeProgram runs a series of optimizations and transformations that are // optimizeProgram runs a series of optimizations and transformations that are
// needed to convert a program to its final form. Some transformations are not // 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. // optional and must be run as the compiler expects them to run.
@@ -1116,6 +1069,10 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config) error {
} }
} }
if config.GOOS() != "darwin" && !config.UseThinLTO() {
transform.ApplyFunctionSections(mod) // -ffunction-sections
}
// Insert values from -ldflags="-X ..." into the IR. // Insert values from -ldflags="-X ..." into the IR.
err = setGlobalValues(mod, config.Options.GlobalValues) err = setGlobalValues(mod, config.Options.GlobalValues)
if err != nil { if err != nil {
+1
View File
@@ -56,6 +56,7 @@ func TestClangAttributes(t *testing.T) {
{GOOS: "linux", GOARCH: "arm", GOARM: "6"}, {GOOS: "linux", GOARCH: "arm", GOARM: "6"},
{GOOS: "linux", GOARCH: "arm", GOARM: "7"}, {GOOS: "linux", GOARCH: "arm", GOARM: "7"},
{GOOS: "linux", GOARCH: "arm64"}, {GOOS: "linux", GOARCH: "arm64"},
{GOOS: "linux", GOARCH: "mips"},
{GOOS: "darwin", GOARCH: "amd64"}, {GOOS: "darwin", GOARCH: "amd64"},
{GOOS: "darwin", GOARCH: "arm64"}, {GOOS: "darwin", GOARCH: "arm64"},
{GOOS: "windows", GOARCH: "amd64"}, {GOOS: "windows", GOARCH: "amd64"},
+16 -8
View File
@@ -56,7 +56,7 @@ import (
// depfile but without invalidating its name. For this reason, the depfile is // depfile but without invalidating its name. For this reason, the depfile is
// written on each new compilation (even when it seems unnecessary). However, it // 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. // could in rare cases lead to a stale file fetched from the cache.
func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands func(string, ...string)) (string, error) { func compileAndCacheCFile(abspath, tmpdir string, cflags []string, thinlto bool, printCommands func(string, ...string)) (string, error) {
// Hash input file. // Hash input file.
fileHash, err := hashFile(abspath) fileHash, err := hashFile(abspath)
if err != nil { if err != nil {
@@ -67,6 +67,11 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands
unlock := lock(filepath.Join(goenv.Get("GOCACHE"), fileHash+".c.lock")) unlock := lock(filepath.Join(goenv.Get("GOCACHE"), fileHash+".c.lock"))
defer unlock() defer unlock()
ext := ".o"
if thinlto {
ext = ".bc"
}
// Create cache key for the dependencies file. // Create cache key for the dependencies file.
buf, err := json.Marshal(struct { buf, err := json.Marshal(struct {
Path string Path string
@@ -99,7 +104,7 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands
} }
// Obtain hashes of all the files listed as a dependency. // Obtain hashes of all the files listed as a dependency.
outpath, err := makeCFileCachePath(dependencies, depfileNameHash) outpath, err := makeCFileCachePath(dependencies, depfileNameHash, ext)
if err == nil { if err == nil {
if _, err := os.Stat(outpath); err == nil { if _, err := os.Stat(outpath); err == nil {
return outpath, nil return outpath, nil
@@ -112,7 +117,7 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands
return "", err return "", err
} }
objTmpFile, err := os.CreateTemp(goenv.Get("GOCACHE"), "tmp-*.bc") objTmpFile, err := os.CreateTemp(goenv.Get("GOCACHE"), "tmp-*"+ext)
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -122,8 +127,11 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands
return "", err return "", err
} }
depTmpFile.Close() depTmpFile.Close()
flags := append([]string{}, cflags...) // copy cflags flags := append([]string{}, cflags...) // copy cflags
flags = append(flags, "-MD", "-MV", "-MTdeps", "-MF", depTmpFile.Name(), "-flto=thin") // autogenerate dependencies flags = append(flags, "-MD", "-MV", "-MTdeps", "-MF", depTmpFile.Name()) // autogenerate dependencies
if thinlto {
flags = append(flags, "-flto=thin")
}
flags = append(flags, "-c", "-o", objTmpFile.Name(), abspath) flags = append(flags, "-c", "-o", objTmpFile.Name(), abspath)
if strings.ToLower(filepath.Ext(abspath)) == ".s" { if strings.ToLower(filepath.Ext(abspath)) == ".s" {
// If this is an assembly file (.s or .S, lowercase or uppercase), then // If this is an assembly file (.s or .S, lowercase or uppercase), then
@@ -181,7 +189,7 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands
} }
// Move temporary object file to final location. // Move temporary object file to final location.
outpath, err := makeCFileCachePath(dependencySlice, depfileNameHash) outpath, err := makeCFileCachePath(dependencySlice, depfileNameHash, ext)
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -196,7 +204,7 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands
// Create a cache path (a path in GOCACHE) to store the output of a compiler // 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 // 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. // including compiler flags) and the hash of all input files in the paths slice.
func makeCFileCachePath(paths []string, depfileNameHash string) (string, error) { func makeCFileCachePath(paths []string, depfileNameHash, ext string) (string, error) {
// Hash all input files. // Hash all input files.
fileHashes := make(map[string]string, len(paths)) fileHashes := make(map[string]string, len(paths))
for _, path := range paths { for _, path := range paths {
@@ -221,7 +229,7 @@ func makeCFileCachePath(paths []string, depfileNameHash string) (string, error)
outFileNameBuf := sha512.Sum512_224(buf) outFileNameBuf := sha512.Sum512_224(buf)
cacheKey := hex.EncodeToString(outFileNameBuf[:]) cacheKey := hex.EncodeToString(outFileNameBuf[:])
outpath := filepath.Join(goenv.Get("GOCACHE"), "obj-"+cacheKey+".bc") outpath := filepath.Join(goenv.Get("GOCACHE"), "obj-"+cacheKey+ext)
return outpath, nil return outpath, nil
} }
+8 -8
View File
@@ -165,14 +165,6 @@ func (c *Config) OptLevels() (optLevel, sizeLevel int, inlinerThreshold uint) {
} }
} }
// LTO returns one of the possible LTO configurations: legacy or thin.
func (c *Config) LTO() string {
if c.Options.LTO != "" {
return c.Options.LTO
}
return "legacy"
}
// PanicStrategy returns the panic strategy selected for this target. Valid // PanicStrategy returns the panic strategy selected for this target. Valid
// values are "print" (print the panic value, then exit) or "trap" (issue a trap // values are "print" (print the panic value, then exit) or "trap" (issue a trap
// instruction). // instruction).
@@ -199,6 +191,14 @@ func (c *Config) StackSize() uint64 {
return c.Target.DefaultStackSize return c.Target.DefaultStackSize
} }
// UseThinLTO returns whether ThinLTO should be used for the given target.
func (c *Config) UseThinLTO() bool {
// All architectures support ThinLTO now. However, this code is kept for the
// time being in case there are regressions. The non-ThinLTO code support
// should be removed when it is proven to work reliably.
return true
}
// RP2040BootPatch returns whether the RP2040 boot patch should be applied that // RP2040BootPatch returns whether the RP2040 boot patch should be applied that
// calculates and patches in the checksum for the 2nd stage bootloader. // calculates and patches in the checksum for the 2nd stage bootloader.
func (c *Config) RP2040BootPatch() bool { func (c *Config) RP2040BootPatch() bool {
-8
View File
@@ -14,7 +14,6 @@ var (
validPrintSizeOptions = []string{"none", "short", "full"} validPrintSizeOptions = []string{"none", "short", "full"}
validPanicStrategyOptions = []string{"print", "trap"} validPanicStrategyOptions = []string{"print", "trap"}
validOptOptions = []string{"none", "0", "1", "2", "s", "z"} validOptOptions = []string{"none", "0", "1", "2", "s", "z"}
validLTOOptions = []string{"legacy", "thin"}
) )
// Options contains extra options to give to the compiler. These options are // Options contains extra options to give to the compiler. These options are
@@ -26,7 +25,6 @@ type Options struct {
GOARM string // environment variable (only used with GOARCH=arm) GOARM string // environment variable (only used with GOARCH=arm)
Target string Target string
Opt string Opt string
LTO string
GC string GC string
PanicStrategy string PanicStrategy string
Scheduler string Scheduler string
@@ -109,12 +107,6 @@ func (o *Options) Verify() error {
} }
} }
if o.LTO != "" {
if !isInArray(validLTOOptions, o.LTO) {
return fmt.Errorf("invalid -lto=%s: valid values are %s", o.LTO, strings.Join(validLTOOptions, ", "))
}
}
return nil return nil
} }
+5
View File
@@ -278,6 +278,9 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
case "arm64": case "arm64":
spec.CPU = "generic" spec.CPU = "generic"
spec.Features = "+neon" spec.Features = "+neon"
case "mips":
spec.CPU = "mips32r2"
spec.Features = "+mips32r2,-noabicalls"
} }
if goos == "darwin" { if goos == "darwin" {
spec.Linker = "ld.lld" spec.Linker = "ld.lld"
@@ -350,6 +353,8 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
spec.Emulator = "qemu-arm {}" spec.Emulator = "qemu-arm {}"
case "arm64": case "arm64":
spec.Emulator = "qemu-aarch64 {}" spec.Emulator = "qemu-aarch64 {}"
case "mips":
spec.Emulator = "qemu-mips {}"
} }
} }
} }
+1 -3
View File
@@ -34,9 +34,7 @@ var stdlibAliases = map[string]string{
// createAlias implements the function (in the builder) as a call to the alias // createAlias implements the function (in the builder) as a call to the alias
// function. // function.
func (b *builder) createAlias(alias llvm.Value) { func (b *builder) createAlias(alias llvm.Value) {
if !b.LTO { b.llvmFn.SetVisibility(llvm.HiddenVisibility)
b.llvmFn.SetVisibility(llvm.HiddenVisibility)
}
b.llvmFn.SetUnnamedAddr(true) b.llvmFn.SetUnnamedAddr(true)
if b.Debug { if b.Debug {
+5 -24
View File
@@ -47,7 +47,6 @@ type Config struct {
CodeModel string CodeModel string
RelocationModel string RelocationModel string
SizeLevel int SizeLevel int
TinyGoVersion string // for llvm.ident
// Various compiler options that determine how code is generated. // Various compiler options that determine how code is generated.
Scheduler string Scheduler string
@@ -55,7 +54,6 @@ type Config struct {
DefaultStackSize uint64 DefaultStackSize uint64
NeedsStackObjects bool NeedsStackObjects bool
Debug bool // Whether to emit debug information in the LLVM module. Debug bool // Whether to emit debug information in the LLVM module.
LTO bool // non-legacy LTO (meaning: package bitcode is merged by the linker)
} }
// compilerContext contains function-independent data that should still be // compilerContext contains function-independent data that should still be
@@ -323,14 +321,6 @@ func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package,
llvm.ConstInt(c.ctx.Int32Type(), 4, false).ConstantAsMetadata(), llvm.ConstInt(c.ctx.Int32Type(), 4, false).ConstantAsMetadata(),
}), }),
) )
if c.TinyGoVersion != "" {
// It is necessary to set llvm.ident, otherwise debugging on MacOS
// won't work.
c.mod.AddNamedMetadataOperand("llvm.ident",
c.ctx.MDNode(([]llvm.Metadata{
c.ctx.MDString("TinyGo version " + c.TinyGoVersion),
})))
}
c.dibuilder.Finalize() c.dibuilder.Finalize()
c.dibuilder.Destroy() c.dibuilder.Destroy()
} }
@@ -898,9 +888,7 @@ func (c *compilerContext) createPackage(irbuilder llvm.Builder, pkg *ssa.Package
c.createEmbedGlobal(member, global, files) c.createEmbedGlobal(member, global, files)
} else if !info.extern { } else if !info.extern {
global.SetInitializer(llvm.ConstNull(global.GlobalValueType())) global.SetInitializer(llvm.ConstNull(global.GlobalValueType()))
if !c.LTO { global.SetVisibility(llvm.HiddenVisibility)
global.SetVisibility(llvm.HiddenVisibility)
}
if info.section != "" { if info.section != "" {
global.SetSection(info.section) global.SetSection(info.section)
} }
@@ -947,9 +935,7 @@ func (c *compilerContext) createEmbedGlobal(member *ssa.Global, global llvm.Valu
} }
strObj := c.getEmbedFileString(files[0]) strObj := c.getEmbedFileString(files[0])
global.SetInitializer(strObj) global.SetInitializer(strObj)
if !c.LTO { global.SetVisibility(llvm.HiddenVisibility)
global.SetVisibility(llvm.HiddenVisibility)
}
case *types.Slice: case *types.Slice:
if typ.Elem().Underlying().(*types.Basic).Kind() != types.Byte { if typ.Elem().Underlying().(*types.Basic).Kind() != types.Byte {
@@ -973,9 +959,7 @@ func (c *compilerContext) createEmbedGlobal(member *ssa.Global, global llvm.Valu
sliceLen := llvm.ConstInt(c.uintptrType, file.Size, false) sliceLen := llvm.ConstInt(c.uintptrType, file.Size, false)
sliceObj := c.ctx.ConstStruct([]llvm.Value{slicePtr, sliceLen, sliceLen}, false) sliceObj := c.ctx.ConstStruct([]llvm.Value{slicePtr, sliceLen, sliceLen}, false)
global.SetInitializer(sliceObj) global.SetInitializer(sliceObj)
if !c.LTO { global.SetVisibility(llvm.HiddenVisibility)
global.SetVisibility(llvm.HiddenVisibility)
}
case *types.Struct: case *types.Struct:
// Assume this is an embed.FS struct: // Assume this is an embed.FS struct:
@@ -1058,9 +1042,7 @@ func (c *compilerContext) createEmbedGlobal(member *ssa.Global, global llvm.Valu
globalInitializer := llvm.ConstNull(c.getLLVMType(member.Type().(*types.Pointer).Elem())) globalInitializer := llvm.ConstNull(c.getLLVMType(member.Type().(*types.Pointer).Elem()))
globalInitializer = c.builder.CreateInsertValue(globalInitializer, sliceGlobal, 0, "") globalInitializer = c.builder.CreateInsertValue(globalInitializer, sliceGlobal, 0, "")
global.SetInitializer(globalInitializer) global.SetInitializer(globalInitializer)
if !c.LTO { global.SetVisibility(llvm.HiddenVisibility)
global.SetVisibility(llvm.HiddenVisibility)
}
global.SetAlignment(c.targetData.ABITypeAlignment(globalInitializer.Type())) global.SetAlignment(c.targetData.ABITypeAlignment(globalInitializer.Type()))
} }
} }
@@ -1107,8 +1089,7 @@ func (b *builder) createFunctionStart(intrinsic bool) {
// assertion error in llvm-project/llvm/include/llvm/IR/GlobalValue.h:236 // assertion error in llvm-project/llvm/include/llvm/IR/GlobalValue.h:236
// is thrown. // is thrown.
if b.llvmFn.Linkage() != llvm.InternalLinkage && if b.llvmFn.Linkage() != llvm.InternalLinkage &&
b.llvmFn.Linkage() != llvm.PrivateLinkage && b.llvmFn.Linkage() != llvm.PrivateLinkage {
!b.LTO {
b.llvmFn.SetVisibility(llvm.HiddenVisibility) b.llvmFn.SetVisibility(llvm.HiddenVisibility)
} }
b.llvmFn.SetUnnamedAddr(true) b.llvmFn.SetUnnamedAddr(true)
-1
View File
@@ -49,7 +49,6 @@ func TestCompiler(t *testing.T) {
{"goroutine.go", "cortex-m-qemu", "tasks"}, {"goroutine.go", "cortex-m-qemu", "tasks"},
{"channel.go", "", ""}, {"channel.go", "", ""},
{"gc.go", "", ""}, {"gc.go", "", ""},
{"zeromap.go", "", ""},
} }
if goMinor >= 20 { if goMinor >= 20 {
tests = append(tests, testCase{"go1.20.go", "", ""}) tests = append(tests, testCase{"go1.20.go", "", ""})
+1 -1
View File
@@ -31,7 +31,7 @@ func (b *builder) supportsRecover() bool {
// proposal of WebAssembly: // proposal of WebAssembly:
// https://github.com/WebAssembly/exception-handling // https://github.com/WebAssembly/exception-handling
return false return false
case "riscv64", "xtensa": case "riscv64", "xtensa", "mips":
// TODO: add support for these architectures // TODO: add support for these architectures
return false return false
default: default:
+10 -15
View File
@@ -490,22 +490,17 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
commaOk = b.CreateCall(fn.GlobalValueType(), fn, []llvm.Value{actualTypeNum}, "") commaOk = b.CreateCall(fn.GlobalValueType(), fn, []llvm.Value{actualTypeNum}, "")
} else { } else {
if b.LTO { globalName := "reflect/types.typeid:" + getTypeCodeName(expr.AssertedType)
assertedTypeCodeGlobal := b.getTypeCode(expr.AssertedType) assertedTypeCodeGlobal := b.mod.NamedGlobal(globalName)
commaOk = b.CreateICmp(llvm.IntEQ, actualTypeNum, assertedTypeCodeGlobal, "commaok") if assertedTypeCodeGlobal.IsNil() {
} else { // Create a new typecode global.
globalName := "reflect/types.typeid:" + getTypeCodeName(expr.AssertedType) assertedTypeCodeGlobal = llvm.AddGlobal(b.mod, b.ctx.Int8Type(), globalName)
assertedTypeCodeGlobal := b.mod.NamedGlobal(globalName) assertedTypeCodeGlobal.SetGlobalConstant(true)
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.
commaOk = b.createRuntimeCall("typeAssert", []llvm.Value{actualTypeNum, assertedTypeCodeGlobal}, "typecode")
} }
// Type assert on concrete type.
// Call runtime.typeAssert, which will be lowered to a simple icmp or
// const false in the interface lowering pass.
commaOk = b.createRuntimeCall("typeAssert", []llvm.Value{actualTypeNum, assertedTypeCodeGlobal}, "typecode")
} }
// Add 2 new basic blocks (that should get optimized away): one for the // Add 2 new basic blocks (that should get optimized away): one for the
+1 -3
View File
@@ -45,9 +45,7 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
globalLLVMType := b.getLLVMType(globalType) globalLLVMType := b.getLLVMType(globalType)
globalName := b.fn.Package().Pkg.Path() + "$interrupt" + strconv.FormatInt(id.Int64(), 10) globalName := b.fn.Package().Pkg.Path() + "$interrupt" + strconv.FormatInt(id.Int64(), 10)
global := llvm.AddGlobal(b.mod, globalLLVMType, globalName) global := llvm.AddGlobal(b.mod, globalLLVMType, globalName)
if !b.LTO { global.SetVisibility(llvm.HiddenVisibility)
global.SetVisibility(llvm.HiddenVisibility)
}
global.SetGlobalConstant(true) global.SetGlobalConstant(true)
global.SetUnnamedAddr(true) global.SetUnnamedAddr(true)
initializer := llvm.ConstNull(globalLLVMType) initializer := llvm.ConstNull(globalLLVMType)
+1 -78
View File
@@ -89,7 +89,6 @@ func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Val
// growth. // growth.
mapKeyAlloca, mapKeyPtr, mapKeySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") mapKeyAlloca, mapKeyPtr, mapKeySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, mapKeyAlloca) b.CreateStore(key, mapKeyAlloca)
b.zeroUndefBytes(b.getLLVMType(keyType), mapKeyAlloca)
// Fetch the value from the hashmap. // Fetch the value from the hashmap.
params := []llvm.Value{m, mapKeyPtr, mapValuePtr, mapValueSize} params := []llvm.Value{m, mapKeyPtr, mapValuePtr, mapValueSize}
commaOkValue = b.createRuntimeCall("hashmapBinaryGet", params, "") commaOkValue = b.createRuntimeCall("hashmapBinaryGet", params, "")
@@ -134,7 +133,6 @@ func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value,
// key can be compared with runtime.memequal // key can be compared with runtime.memequal
keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, keyAlloca) b.CreateStore(key, keyAlloca)
b.zeroUndefBytes(b.getLLVMType(keyType), keyAlloca)
params := []llvm.Value{m, keyPtr, valuePtr} params := []llvm.Value{m, keyPtr, valuePtr}
b.createRuntimeCall("hashmapBinarySet", params, "") b.createRuntimeCall("hashmapBinarySet", params, "")
b.emitLifetimeEnd(keyPtr, keySize) b.emitLifetimeEnd(keyPtr, keySize)
@@ -163,7 +161,6 @@ func (b *builder) createMapDelete(keyType types.Type, m, key llvm.Value, pos tok
} else if hashmapIsBinaryKey(keyType) { } else if hashmapIsBinaryKey(keyType) {
keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, keyAlloca) b.CreateStore(key, keyAlloca)
b.zeroUndefBytes(b.getLLVMType(keyType), keyAlloca)
params := []llvm.Value{m, keyPtr} params := []llvm.Value{m, keyPtr}
b.createRuntimeCall("hashmapBinaryDelete", params, "") b.createRuntimeCall("hashmapBinaryDelete", params, "")
b.emitLifetimeEnd(keyPtr, keySize) b.emitLifetimeEnd(keyPtr, keySize)
@@ -243,8 +240,7 @@ func (b *builder) createMapIteratorNext(rangeVal ssa.Value, llvmRangeVal, it llv
} }
// Returns true if this key type does not contain strings, interfaces etc., so // Returns true if this key type does not contain strings, interfaces etc., so
// can be compared with runtime.memequal. Note that padding bytes are undef // can be compared with runtime.memequal.
// and can alter two "equal" structs being equal when compared with memequal.
func hashmapIsBinaryKey(keyType types.Type) bool { func hashmapIsBinaryKey(keyType types.Type) bool {
switch keyType := keyType.(type) { switch keyType := keyType.(type) {
case *types.Basic: case *types.Basic:
@@ -267,76 +263,3 @@ func hashmapIsBinaryKey(keyType types.Type) bool {
return false return false
} }
} }
func (b *builder) zeroUndefBytes(llvmType llvm.Type, ptr llvm.Value) error {
// We know that hashmapIsBinaryKey is true, so we only have to handle those types that can show up there.
// To zero all undefined bytes, we iterate over all the fields in the type. For each element, compute the
// offset of that element. If it's Basic type, there are no internal padding bytes. For compound types, we recurse to ensure
// we handle nested types. Next, we determine if there are any padding bytes before the next
// element and zero those as well.
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
switch llvmType.TypeKind() {
case llvm.IntegerTypeKind:
// no padding bytes
return nil
case llvm.PointerTypeKind:
// mo padding bytes
return nil
case llvm.ArrayTypeKind:
llvmArrayType := llvmType
llvmElemType := llvmType.ElementType()
for i := 0; i < llvmArrayType.ArrayLength(); i++ {
idx := llvm.ConstInt(b.uintptrType, uint64(i), false)
elemPtr := b.CreateInBoundsGEP(llvmArrayType, ptr, []llvm.Value{zero, idx}, "")
// zero any padding bytes in this element
b.zeroUndefBytes(llvmElemType, elemPtr)
}
case llvm.StructTypeKind:
llvmStructType := llvmType
numFields := llvmStructType.StructElementTypesCount()
llvmElementTypes := llvmStructType.StructElementTypes()
for i := 0; i < numFields; i++ {
idx := llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)
elemPtr := b.CreateInBoundsGEP(llvmStructType, ptr, []llvm.Value{zero, idx}, "")
// zero any padding bytes in this field
llvmElemType := llvmElementTypes[i]
b.zeroUndefBytes(llvmElemType, elemPtr)
// zero any padding bytes before the next field, if any
offset := b.targetData.ElementOffset(llvmStructType, i)
storeSize := b.targetData.TypeStoreSize(llvmElemType)
fieldEndOffset := offset + storeSize
var nextOffset uint64
if i < numFields-1 {
nextOffset = b.targetData.ElementOffset(llvmStructType, i+1)
} else {
// Last field? Next offset is the total size of the allcoate struct.
nextOffset = b.targetData.TypeAllocSize(llvmStructType)
}
if fieldEndOffset != nextOffset {
n := llvm.ConstInt(b.uintptrType, nextOffset-fieldEndOffset, false)
llvmStoreSize := llvm.ConstInt(b.uintptrType, storeSize, false)
gepPtr := elemPtr
if gepPtr.Type() != b.i8ptrType {
gepPtr = b.CreateBitCast(gepPtr, b.i8ptrType, "") // LLVM 14
}
paddingStart := b.CreateInBoundsGEP(b.ctx.Int8Type(), gepPtr, []llvm.Value{llvmStoreSize}, "")
if paddingStart.Type() != b.i8ptrType {
paddingStart = b.CreateBitCast(paddingStart, b.i8ptrType, "") // LLVM 14
}
b.createRuntimeCall("memzero", []llvm.Value{paddingStart, n}, "")
}
}
}
return nil
}
-37
View File
@@ -1,37 +0,0 @@
package main
type hasPadding struct {
b1 bool
i int
b2 bool
}
type nestedPadding struct {
b bool
hasPadding
i int
}
//go:noinline
func testZeroGet(m map[hasPadding]int, s hasPadding) int {
return m[s]
}
//go:noinline
func testZeroSet(m map[hasPadding]int, s hasPadding) {
m[s] = 5
}
//go:noinline
func testZeroArrayGet(m map[[2]hasPadding]int, s [2]hasPadding) int {
return m[s]
}
//go:noinline
func testZeroArraySet(m map[[2]hasPadding]int, s [2]hasPadding) {
m[s] = 5
}
func main() {
}
-170
View File
@@ -1,170 +0,0 @@
; ModuleID = 'zeromap.go'
source_filename = "zeromap.go"
target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20"
target triple = "wasm32-unknown-wasi"
%main.hasPadding = type { i1, i32, i1 }
declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0
declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #0
; Function Attrs: nounwind
define hidden void @main.init(ptr %context) unnamed_addr #1 {
entry:
ret void
}
; Function Attrs: noinline nounwind
define hidden i32 @main.testZeroGet(ptr dereferenceable_or_null(40) %m, i1 %s.b1, i32 %s.i, i1 %s.b2, ptr %context) unnamed_addr #2 {
entry:
%hashmap.key = alloca %main.hasPadding, align 8
%hashmap.value = alloca i32, align 4
%s = alloca %main.hasPadding, align 8
%0 = insertvalue %main.hasPadding zeroinitializer, i1 %s.b1, 0
%1 = insertvalue %main.hasPadding %0, i32 %s.i, 1
%2 = insertvalue %main.hasPadding %1, i1 %s.b2, 2
%stackalloc = alloca i8, align 1
store %main.hasPadding zeroinitializer, ptr %s, align 8
call void @runtime.trackPointer(ptr nonnull %s, ptr nonnull %stackalloc, ptr undef) #4
store %main.hasPadding %2, ptr %s, align 8
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %hashmap.value)
call void @llvm.lifetime.start.p0(i64 12, ptr nonnull %hashmap.key)
store %main.hasPadding %2, ptr %hashmap.key, align 8
%3 = getelementptr inbounds i8, ptr %hashmap.key, i32 1
call void @runtime.memzero(ptr nonnull %3, i32 3, ptr undef) #4
%4 = getelementptr inbounds i8, ptr %hashmap.key, i32 9
call void @runtime.memzero(ptr nonnull %4, i32 3, ptr undef) #4
%5 = call i1 @runtime.hashmapBinaryGet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, i32 4, ptr undef) #4
call void @llvm.lifetime.end.p0(i64 12, ptr nonnull %hashmap.key)
%6 = load i32, ptr %hashmap.value, align 4
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %hashmap.value)
ret i32 %6
}
; Function Attrs: argmemonly nocallback nofree nosync nounwind willreturn
declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) #3
declare void @runtime.memzero(ptr, i32, ptr) #0
declare i1 @runtime.hashmapBinaryGet(ptr dereferenceable_or_null(40), ptr, ptr, i32, ptr) #0
; Function Attrs: argmemonly nocallback nofree nosync nounwind willreturn
declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture) #3
; Function Attrs: noinline nounwind
define hidden void @main.testZeroSet(ptr dereferenceable_or_null(40) %m, i1 %s.b1, i32 %s.i, i1 %s.b2, ptr %context) unnamed_addr #2 {
entry:
%hashmap.key = alloca %main.hasPadding, align 8
%hashmap.value = alloca i32, align 4
%s = alloca %main.hasPadding, align 8
%0 = insertvalue %main.hasPadding zeroinitializer, i1 %s.b1, 0
%1 = insertvalue %main.hasPadding %0, i32 %s.i, 1
%2 = insertvalue %main.hasPadding %1, i1 %s.b2, 2
%stackalloc = alloca i8, align 1
store %main.hasPadding zeroinitializer, ptr %s, align 8
call void @runtime.trackPointer(ptr nonnull %s, ptr nonnull %stackalloc, ptr undef) #4
store %main.hasPadding %2, ptr %s, align 8
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %hashmap.value)
store i32 5, ptr %hashmap.value, align 4
call void @llvm.lifetime.start.p0(i64 12, ptr nonnull %hashmap.key)
store %main.hasPadding %2, ptr %hashmap.key, align 8
%3 = getelementptr inbounds i8, ptr %hashmap.key, i32 1
call void @runtime.memzero(ptr nonnull %3, i32 3, ptr undef) #4
%4 = getelementptr inbounds i8, ptr %hashmap.key, i32 9
call void @runtime.memzero(ptr nonnull %4, i32 3, ptr undef) #4
call void @runtime.hashmapBinarySet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, ptr undef) #4
call void @llvm.lifetime.end.p0(i64 12, ptr nonnull %hashmap.key)
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %hashmap.value)
ret void
}
declare void @runtime.hashmapBinarySet(ptr dereferenceable_or_null(40), ptr, ptr, ptr) #0
; Function Attrs: noinline nounwind
define hidden i32 @main.testZeroArrayGet(ptr dereferenceable_or_null(40) %m, [2 x %main.hasPadding] %s, ptr %context) unnamed_addr #2 {
entry:
%hashmap.key = alloca [2 x %main.hasPadding], align 8
%hashmap.value = alloca i32, align 4
%s1 = alloca [2 x %main.hasPadding], align 8
%stackalloc = alloca i8, align 1
store %main.hasPadding zeroinitializer, ptr %s1, align 8
%s1.repack2 = getelementptr inbounds [2 x %main.hasPadding], ptr %s1, i32 0, i32 1
store %main.hasPadding zeroinitializer, ptr %s1.repack2, align 4
call void @runtime.trackPointer(ptr nonnull %s1, ptr nonnull %stackalloc, ptr undef) #4
%s.elt = extractvalue [2 x %main.hasPadding] %s, 0
store %main.hasPadding %s.elt, ptr %s1, align 8
%s1.repack3 = getelementptr inbounds [2 x %main.hasPadding], ptr %s1, i32 0, i32 1
%s.elt4 = extractvalue [2 x %main.hasPadding] %s, 1
store %main.hasPadding %s.elt4, ptr %s1.repack3, align 4
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %hashmap.value)
call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %hashmap.key)
%s.elt7 = extractvalue [2 x %main.hasPadding] %s, 0
store %main.hasPadding %s.elt7, ptr %hashmap.key, align 8
%hashmap.key.repack8 = getelementptr inbounds [2 x %main.hasPadding], ptr %hashmap.key, i32 0, i32 1
%s.elt9 = extractvalue [2 x %main.hasPadding] %s, 1
store %main.hasPadding %s.elt9, ptr %hashmap.key.repack8, align 4
%0 = getelementptr inbounds i8, ptr %hashmap.key, i32 1
call void @runtime.memzero(ptr nonnull %0, i32 3, ptr undef) #4
%1 = getelementptr inbounds i8, ptr %hashmap.key, i32 9
call void @runtime.memzero(ptr nonnull %1, i32 3, ptr undef) #4
%2 = getelementptr inbounds i8, ptr %hashmap.key, i32 13
call void @runtime.memzero(ptr nonnull %2, i32 3, ptr undef) #4
%3 = getelementptr inbounds i8, ptr %hashmap.key, i32 21
call void @runtime.memzero(ptr nonnull %3, i32 3, ptr undef) #4
%4 = call i1 @runtime.hashmapBinaryGet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, i32 4, ptr undef) #4
call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %hashmap.key)
%5 = load i32, ptr %hashmap.value, align 4
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %hashmap.value)
ret i32 %5
}
; Function Attrs: noinline nounwind
define hidden void @main.testZeroArraySet(ptr dereferenceable_or_null(40) %m, [2 x %main.hasPadding] %s, ptr %context) unnamed_addr #2 {
entry:
%hashmap.key = alloca [2 x %main.hasPadding], align 8
%hashmap.value = alloca i32, align 4
%s1 = alloca [2 x %main.hasPadding], align 8
%stackalloc = alloca i8, align 1
store %main.hasPadding zeroinitializer, ptr %s1, align 8
%s1.repack2 = getelementptr inbounds [2 x %main.hasPadding], ptr %s1, i32 0, i32 1
store %main.hasPadding zeroinitializer, ptr %s1.repack2, align 4
call void @runtime.trackPointer(ptr nonnull %s1, ptr nonnull %stackalloc, ptr undef) #4
%s.elt = extractvalue [2 x %main.hasPadding] %s, 0
store %main.hasPadding %s.elt, ptr %s1, align 8
%s1.repack3 = getelementptr inbounds [2 x %main.hasPadding], ptr %s1, i32 0, i32 1
%s.elt4 = extractvalue [2 x %main.hasPadding] %s, 1
store %main.hasPadding %s.elt4, ptr %s1.repack3, align 4
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %hashmap.value)
store i32 5, ptr %hashmap.value, align 4
call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %hashmap.key)
%s.elt7 = extractvalue [2 x %main.hasPadding] %s, 0
store %main.hasPadding %s.elt7, ptr %hashmap.key, align 8
%hashmap.key.repack8 = getelementptr inbounds [2 x %main.hasPadding], ptr %hashmap.key, i32 0, i32 1
%s.elt9 = extractvalue [2 x %main.hasPadding] %s, 1
store %main.hasPadding %s.elt9, ptr %hashmap.key.repack8, align 4
%0 = getelementptr inbounds i8, ptr %hashmap.key, i32 1
call void @runtime.memzero(ptr nonnull %0, i32 3, ptr undef) #4
%1 = getelementptr inbounds i8, ptr %hashmap.key, i32 9
call void @runtime.memzero(ptr nonnull %1, i32 3, ptr undef) #4
%2 = getelementptr inbounds i8, ptr %hashmap.key, i32 13
call void @runtime.memzero(ptr nonnull %2, i32 3, ptr undef) #4
%3 = getelementptr inbounds i8, ptr %hashmap.key, i32 21
call void @runtime.memzero(ptr nonnull %3, i32 3, ptr undef) #4
call void @runtime.hashmapBinarySet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, ptr undef) #4
call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %hashmap.key)
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %hashmap.value)
ret void
}
; Function Attrs: nounwind
define hidden void @main.main(ptr %context) unnamed_addr #1 {
entry:
ret void
}
attributes #0 = { "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
attributes #1 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
attributes #2 = { noinline nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
attributes #3 = { argmemonly nocallback nofree nosync nounwind willreturn }
attributes #4 = { nounwind }
-2
View File
@@ -1357,7 +1357,6 @@ func main() {
command := os.Args[1] command := os.Args[1]
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z") opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
lto := flag.String("lto", "legacy", "LTO mode: legacy or thin")
gc := flag.String("gc", "", "garbage collector to use (none, leaking, conservative)") gc := flag.String("gc", "", "garbage collector to use (none, leaking, conservative)")
panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)") panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)")
scheduler := flag.String("scheduler", "", "which scheduler to use (none, tasks, asyncify)") scheduler := flag.String("scheduler", "", "which scheduler to use (none, tasks, asyncify)")
@@ -1460,7 +1459,6 @@ func main() {
Target: *target, Target: *target,
StackSize: stackSize, StackSize: stackSize,
Opt: *opt, Opt: *opt,
LTO: *lto,
GC: *gc, GC: *gc,
PanicStrategy: *panicStrategy, PanicStrategy: *panicStrategy,
Scheduler: *scheduler, Scheduler: *scheduler,
+10 -13
View File
@@ -34,6 +34,7 @@ var supportedLinuxArches = map[string]string{
"X86Linux": "linux/386", "X86Linux": "linux/386",
"ARMLinux": "linux/arm/6", "ARMLinux": "linux/arm/6",
"ARM64Linux": "linux/arm64", "ARM64Linux": "linux/arm64",
"MIPSLinux": "linux/mips",
} }
var sema = make(chan struct{}, runtime.NumCPU()) var sema = make(chan struct{}, runtime.NumCPU())
@@ -105,13 +106,6 @@ func TestBuild(t *testing.T) {
runTestWithConfig("print.go", t, opts, nil, nil) runTestWithConfig("print.go", t, opts, nil, nil)
}) })
t.Run("lto=thin", func(t *testing.T) {
t.Parallel()
opts := optionsFromTarget("", sema)
opts.LTO = "thin"
runTestWithConfig("init.go", t, opts, nil, nil)
})
t.Run("ldflags", func(t *testing.T) { t.Run("ldflags", func(t *testing.T) {
t.Parallel() t.Parallel()
opts := optionsFromTarget("", sema) opts := optionsFromTarget("", sema)
@@ -187,8 +181,7 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) {
// Skip the ones that aren't. // Skip the ones that aren't.
switch name { switch name {
case "reflect.go": case "reflect.go":
// Reflect tests do not run correctly, probably because of the // Reflect tests do not work due to type code issues.
// limited amount of memory.
continue continue
case "gc.go": case "gc.go":
@@ -196,16 +189,20 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) {
continue continue
case "json.go", "stdlib.go", "testing.go": case "json.go", "stdlib.go", "testing.go":
// Too big for AVR. Doesn't fit in flash/RAM. // Breaks interp.
continue continue
case "math.go": case "math.go":
// Needs newer picolibc version (for sqrt). // Stuck somewhere, not sure what's happening.
continue continue
case "cgo/": case "cgo/":
// CGo function pointers don't work on AVR (needs LLVM 16 and // CGo does not work on AVR.
// some compiler changes). continue
case "timers.go":
// Doesn't compile:
// panic: compiler: could not store type code number inside interface type code
continue continue
default: default:
-35
View File
@@ -1,35 +0,0 @@
//go:build rp2040
package main
// This example demonstrates scheduling a delayed interrupt by real time clock.
//
// An interrupt may execute user callback function or used for its side effects
// like waking up from sleep or dormant states.
//
// The interrupt can be configured to repeat.
//
// There is no separate method to disable interrupt, use 0 delay for that.
//
// Unfortunately, it is not possible to use time.Duration to work with RTC directly,
// that would introduce a circular dependency between "machine" and "time" packages.
import (
"fmt"
"machine"
"time"
)
func main() {
// Schedule and enable recurring interrupt.
// The callback function is executed in the context of an interrupt handler,
// so regular restructions for this sort of code apply: no blocking, no memory allocation, etc.
delay := time.Minute + 12*time.Second
machine.RTC.SetInterrupt(uint32(delay.Seconds()), true, func() { println("Peekaboo!") })
for {
fmt.Printf("%v\r\n", time.Now().Format(time.RFC3339))
time.Sleep(1 * time.Second)
}
}
+1 -7
View File
@@ -2,10 +2,7 @@
package task package task
import ( import "unsafe"
"runtime/interrupt"
"unsafe"
)
//go:linkname runtimePanic runtime.runtimePanic //go:linkname runtimePanic runtime.runtimePanic
func runtimePanic(str string) func runtimePanic(str string)
@@ -48,9 +45,6 @@ func Pause() {
if *currentTask.state.canaryPtr != stackCanary { if *currentTask.state.canaryPtr != stackCanary {
runtimePanic("goroutine stack overflow") runtimePanic("goroutine stack overflow")
} }
if interrupt.In() {
runtimePanic("blocked inside interrupt")
}
currentTask.state.pause() currentTask.state.pause()
} }
+69
View File
@@ -0,0 +1,69 @@
.section .text.tinygo_startTask
.global tinygo_startTask
.type tinygo_startTask, %function
tinygo_startTask:
// Small assembly stub for starting a goroutine. This is already run on the
// new stack, with the callee-saved registers already loaded.
// Most importantly, s0 contains the pc of the to-be-started function and s1
// contains the only argument it is given. Multiple arguments are packed
// into one by storing them in a new allocation.
// Set the first argument of the goroutine start wrapper, which contains all
// the arguments.
move $a0, $s1
// Branch to the "goroutine start" function. Use jalr to write the return
// address to ra so we'll return here after the goroutine exits.
jalr $s0
nop
nop
// After return, exit this goroutine. This is a tail call.
j tinygo_pause
nop
nop
.section .text.tinygo_swapTask
.global tinygo_swapTask
.type tinygo_swapTask, %function
tinygo_swapTask:
// This function gets the following parameters:
// a0 = newStack uintptr
// a1 = oldStack *uintptr
// Push all callee-saved registers.
addiu $sp, $sp, -40
sw $ra, 36($sp)
sw $s8, 32($sp)
sw $s7, 28($sp)
sw $s6, 24($sp)
sw $s5, 20($sp)
sw $s4, 16($sp)
sw $s3, 12($sp)
sw $s2, 8($sp)
sw $s1, 4($sp)
sw $s0, ($sp)
// Save the current stack pointer in oldStack.
sw $sp, 0($a1)
// Switch to the new stack pointer.
move $sp, $a0
// Pop all saved registers from this new stack.
lw $ra, 36($sp)
lw $s8, 32($sp)
lw $s7, 28($sp)
lw $s6, 24($sp)
lw $s5, 20($sp)
lw $s4, 16($sp)
lw $s3, 12($sp)
lw $s2, 8($sp)
lw $s1, 4($sp)
lw $s0, ($sp)
addiu $sp, $sp, 40
// Return into the task.
jalr $ra
nop
nop
+61
View File
@@ -0,0 +1,61 @@
//go:build scheduler.tasks && mips
package task
import "unsafe"
var systemStack uintptr
// calleeSavedRegs is the list of registers that must be saved and restored when
// switching between tasks. Also see task_stack_mips.S that relies on the exact
// layout of this struct.
type calleeSavedRegs struct {
s0 uintptr
s1 uintptr
s2 uintptr
s3 uintptr
s4 uintptr
s5 uintptr
s6 uintptr
s7 uintptr
s8 uintptr
ra uintptr
}
// archInit runs architecture-specific setup for the goroutine startup.
func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) {
// Store the initial sp for the startTask function (implemented in assembly).
s.sp = uintptr(unsafe.Pointer(r))
// Initialize the registers.
// These will be popped off of the stack on the first resume of the goroutine.
// Start the function at tinygo_startTask (defined in src/internal/task/task_stack_mips.S).
// This assembly code calls a function (passed in s0) with a single argument
// (passed in s1). After the function returns, it calls Pause().
r.ra = uintptr(unsafe.Pointer(&startTask))
// Pass the function to call in s0.
// This function is a compiler-generated wrapper which loads arguments out of a struct pointer.
// See createGoroutineStartWrapper (defined in compiler/goroutine.go) for more information.
r.s0 = fn
// Pass the pointer to the arguments struct in s1.
r.s1 = uintptr(args)
}
func (s *state) resume() {
swapTask(s.sp, &systemStack)
}
func (s *state) pause() {
newStack := systemStack
systemStack = 0
swapTask(newStack, &s.sp)
}
// SystemStack returns the system stack pointer when called from a task stack.
// When called from the system stack, it returns 0.
func SystemStack() uintptr {
return systemStack
}
-13
View File
@@ -56,18 +56,6 @@ var (
} }
DefaultUART = UART0 DefaultUART = UART0
// Since we treat UART1 as zero, let's also call it by the real name
UART1 = UART0
// UART2
UART2 = &_UART2
_UART2 = UART{
Buffer: NewRingBuffer(),
Bus: stm32.USART2,
TxAltFuncSelector: AF7_USART1_2,
RxAltFuncSelector: AF7_USART1_2,
}
// I2C Busses // I2C Busses
I2C1 = &I2C{ I2C1 = &I2C{
Bus: stm32.I2C1, Bus: stm32.I2C1,
@@ -84,5 +72,4 @@ var (
func init() { func init() {
// Enable UARTs Interrupts // Enable UARTs Interrupts
UART0.Interrupt = interrupt.New(stm32.IRQ_USART1, _UART0.handleInterrupt) UART0.Interrupt = interrupt.New(stm32.IRQ_USART1, _UART0.handleInterrupt)
UART2.Interrupt = interrupt.New(stm32.IRQ_USART2, _UART2.handleInterrupt)
} }
+4 -4
View File
@@ -50,11 +50,11 @@ const (
// I2C pins // I2C pins
const ( const (
I2C0_SDA_PIN Pin = D2 I2C0_SDA_PIN Pin = D4
I2C0_SCL_PIN Pin = D3 I2C0_SCL_PIN Pin = D5
I2C1_SDA_PIN Pin = D4 I2C1_SDA_PIN Pin = NoPin
I2C1_SCL_PIN Pin = D5 I2C1_SCL_PIN Pin = NoPin
) )
// SPI pins // SPI pins
+28 -28
View File
@@ -406,61 +406,61 @@ func (p Pin) getPinCfg() uint8 {
return uint8(sam.PORT.PINCFG1_0.Get()>>16) & 0xff return uint8(sam.PORT.PINCFG1_0.Get()>>16) & 0xff
case 35: // PB03 case 35: // PB03
return uint8(sam.PORT.PINCFG1_0.Get()>>24) & 0xff return uint8(sam.PORT.PINCFG1_0.Get()>>24) & 0xff
case 36: // PB04 case 37: // PB04
return uint8(sam.PORT.PINCFG1_4.Get()>>0) & 0xff return uint8(sam.PORT.PINCFG1_4.Get()>>0) & 0xff
case 37: // PB05 case 38: // PB05
return uint8(sam.PORT.PINCFG1_4.Get()>>8) & 0xff return uint8(sam.PORT.PINCFG1_4.Get()>>8) & 0xff
case 38: // PB06 case 39: // PB06
return uint8(sam.PORT.PINCFG1_4.Get()>>16) & 0xff return uint8(sam.PORT.PINCFG1_4.Get()>>16) & 0xff
case 39: // PB07 case 40: // PB07
return uint8(sam.PORT.PINCFG1_4.Get()>>24) & 0xff return uint8(sam.PORT.PINCFG1_4.Get()>>24) & 0xff
case 40: // PB08 case 41: // PB08
return uint8(sam.PORT.PINCFG1_8.Get()>>0) & 0xff return uint8(sam.PORT.PINCFG1_8.Get()>>0) & 0xff
case 41: // PB09 case 42: // PB09
return uint8(sam.PORT.PINCFG1_8.Get()>>8) & 0xff return uint8(sam.PORT.PINCFG1_8.Get()>>8) & 0xff
case 42: // PB10 case 43: // PB10
return uint8(sam.PORT.PINCFG1_8.Get()>>16) & 0xff return uint8(sam.PORT.PINCFG1_8.Get()>>16) & 0xff
case 43: // PB11 case 44: // PB11
return uint8(sam.PORT.PINCFG1_8.Get()>>24) & 0xff return uint8(sam.PORT.PINCFG1_8.Get()>>24) & 0xff
case 44: // PB12 case 45: // PB12
return uint8(sam.PORT.PINCFG1_12.Get()>>0) & 0xff return uint8(sam.PORT.PINCFG1_12.Get()>>0) & 0xff
case 45: // PB13 case 46: // PB13
return uint8(sam.PORT.PINCFG1_12.Get()>>8) & 0xff return uint8(sam.PORT.PINCFG1_12.Get()>>8) & 0xff
case 46: // PB14 case 47: // PB14
return uint8(sam.PORT.PINCFG1_12.Get()>>16) & 0xff return uint8(sam.PORT.PINCFG1_12.Get()>>16) & 0xff
case 47: // PB15 case 48: // PB15
return uint8(sam.PORT.PINCFG1_12.Get()>>24) & 0xff return uint8(sam.PORT.PINCFG1_12.Get()>>24) & 0xff
case 48: // PB16 case 49: // PB16
return uint8(sam.PORT.PINCFG1_16.Get()>>0) & 0xff return uint8(sam.PORT.PINCFG1_16.Get()>>0) & 0xff
case 49: // PB17 case 50: // PB17
return uint8(sam.PORT.PINCFG1_16.Get()>>8) & 0xff return uint8(sam.PORT.PINCFG1_16.Get()>>8) & 0xff
case 50: // PB18 case 51: // PB18
return uint8(sam.PORT.PINCFG1_16.Get()>>16) & 0xff return uint8(sam.PORT.PINCFG1_16.Get()>>16) & 0xff
case 51: // PB19 case 52: // PB19
return uint8(sam.PORT.PINCFG1_16.Get()>>24) & 0xff return uint8(sam.PORT.PINCFG1_16.Get()>>24) & 0xff
case 52: // PB20 case 53: // PB20
return uint8(sam.PORT.PINCFG1_20.Get()>>0) & 0xff return uint8(sam.PORT.PINCFG1_20.Get()>>0) & 0xff
case 53: // PB21 case 54: // PB21
return uint8(sam.PORT.PINCFG1_20.Get()>>8) & 0xff return uint8(sam.PORT.PINCFG1_20.Get()>>8) & 0xff
case 54: // PB22 case 55: // PB22
return uint8(sam.PORT.PINCFG1_20.Get()>>16) & 0xff return uint8(sam.PORT.PINCFG1_20.Get()>>16) & 0xff
case 55: // PB23 case 56: // PB23
return uint8(sam.PORT.PINCFG1_20.Get()>>24) & 0xff return uint8(sam.PORT.PINCFG1_20.Get()>>24) & 0xff
case 56: // PB24 case 57: // PB24
return uint8(sam.PORT.PINCFG1_24.Get()>>0) & 0xff return uint8(sam.PORT.PINCFG1_24.Get()>>0) & 0xff
case 57: // PB25 case 58: // PB25
return uint8(sam.PORT.PINCFG1_24.Get()>>8) & 0xff return uint8(sam.PORT.PINCFG1_24.Get()>>8) & 0xff
case 58: // PB26 case 59: // PB26
return uint8(sam.PORT.PINCFG1_24.Get()>>16) & 0xff return uint8(sam.PORT.PINCFG1_24.Get()>>16) & 0xff
case 59: // PB27 case 60: // PB27
return uint8(sam.PORT.PINCFG1_24.Get()>>24) & 0xff return uint8(sam.PORT.PINCFG1_24.Get()>>24) & 0xff
case 60: // PB28 case 61: // PB28
return uint8(sam.PORT.PINCFG1_28.Get()>>0) & 0xff return uint8(sam.PORT.PINCFG1_28.Get()>>0) & 0xff
case 61: // PB29 case 62: // PB29
return uint8(sam.PORT.PINCFG1_28.Get()>>8) & 0xff return uint8(sam.PORT.PINCFG1_28.Get()>>8) & 0xff
case 62: // PB30 case 63: // PB30
return uint8(sam.PORT.PINCFG1_28.Get()>>16) & 0xff return uint8(sam.PORT.PINCFG1_28.Get()>>16) & 0xff
case 63: // PB31 case 64: // PB31
return uint8(sam.PORT.PINCFG1_28.Get()>>24) & 0xff return uint8(sam.PORT.PINCFG1_28.Get()>>24) & 0xff
default: default:
return 0 return 0
+1 -20
View File
@@ -57,8 +57,6 @@ var (
ErrInvalidTgtAddr = errors.New("invalid target i2c address not in 0..0x80 or is reserved") ErrInvalidTgtAddr = errors.New("invalid target i2c address not in 0..0x80 or is reserved")
ErrI2CGeneric = errors.New("i2c error") ErrI2CGeneric = errors.New("i2c error")
ErrRP2040I2CDisable = errors.New("i2c rp2040 peripheral timeout in disable") ErrRP2040I2CDisable = errors.New("i2c rp2040 peripheral timeout in disable")
errInvalidI2CSDA = errors.New("invalid I2C SDA pin")
errInvalidI2CSCL = errors.New("invalid I2C SCL pin")
) )
// Tx performs a write and then a read transfer placing the result in // Tx performs a write and then a read transfer placing the result in
@@ -92,7 +90,7 @@ func (i2c *I2C) Tx(addr uint16, w, r []byte) error {
// SCL: 3, 7, 11, 15, 19, 27 // SCL: 3, 7, 11, 15, 19, 27
func (i2c *I2C) Configure(config I2CConfig) error { func (i2c *I2C) Configure(config I2CConfig) error {
const defaultBaud uint32 = 100_000 // 100kHz standard mode const defaultBaud uint32 = 100_000 // 100kHz standard mode
if config.SCL == 0 && config.SDA == 0 { if config.SCL == 0 {
// If config pins are zero valued or clock pin is invalid then we set default values. // If config pins are zero valued or clock pin is invalid then we set default values.
switch i2c.Bus { switch i2c.Bus {
case rp.I2C0: case rp.I2C0:
@@ -103,23 +101,6 @@ func (i2c *I2C) Configure(config I2CConfig) error {
config.SDA = I2C1_SDA_PIN config.SDA = I2C1_SDA_PIN
} }
} }
var okSCL, okSDA bool
switch i2c.Bus {
case rp.I2C0:
okSCL = (config.SCL+3)%4 == 0
okSDA = (config.SDA+4)%4 == 0
case rp.I2C1:
okSCL = (config.SCL+1)%4 == 0
okSDA = (config.SDA+2)%4 == 0
}
switch {
case !okSCL:
return errInvalidI2CSCL
case !okSDA:
return errInvalidI2CSDA
}
if config.Frequency == 0 { if config.Frequency == 0 {
config.Frequency = defaultBaud config.Frequency = defaultBaud
} }
-240
View File
@@ -1,240 +0,0 @@
//go:build rp2040
// Implementation based on code located here:
// https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/hardware_rtc/rtc.c
package machine
import (
"device/rp"
"errors"
"runtime/interrupt"
"unsafe"
)
type rtcType rp.RTC_Type
type rtcTime struct {
Year int16
Month int8
Day int8
Dotw int8
Hour int8
Min int8
Sec int8
}
var RTC = (*rtcType)(unsafe.Pointer(rp.RTC))
const (
second = 1
minute = 60 * second
hour = 60 * minute
day = 24 * hour
)
var (
rtcAlarmRepeats bool
rtcCallback func()
rtcEpoch = rtcTime{
Year: 1970, Month: 1, Day: 1, Dotw: 4, Hour: 0, Min: 0, Sec: 0,
}
)
var (
ErrRtcDelayTooSmall = errors.New("RTC interrupt deplay is too small, shall be at least 1 second")
ErrRtcDelayTooLarge = errors.New("RTC interrupt deplay is too large, shall be no more than 1 day")
)
// SetInterrupt configures delayed and optionally recurring interrupt by real time clock.
//
// Delay is specified in whole seconds, allowed range depends on platform.
// Zero delay disables previously configured interrupt, if any.
//
// RP2040 implementation allows delay to be up to 1 day, otherwise a respective error is emitted.
func (rtc *rtcType) SetInterrupt(delay uint32, repeat bool, callback func()) error {
// Verify delay range
if delay > day {
return ErrRtcDelayTooLarge
}
// De-configure delayed interrupt if delay is zero
if delay == 0 {
rtc.disableInterruptMatch()
return nil
}
// Configure delayed interrupt
rtc.setDivider()
rtcAlarmRepeats = repeat
rtcCallback = callback
err := rtc.setTime(rtcEpoch)
if err != nil {
return err
}
rtc.setAlarm(toAlarmTime(delay), callback)
return nil
}
func toAlarmTime(delay uint32) rtcTime {
result := rtcEpoch
remainder := delay + 1 // needed "+1", otherwise alarm fires one second too early
if remainder >= hour {
result.Hour = int8(remainder / hour)
remainder %= hour
}
if remainder >= minute {
result.Min = int8(remainder / minute)
remainder %= minute
}
result.Sec = int8(remainder)
return result
}
func (rtc *rtcType) setDivider() {
// Get clk_rtc freq and make sure it is running
rtcFreq := configuredFreq[clkRTC]
if rtcFreq == 0 {
panic("can not set RTC divider, clock is not running")
}
// Take rtc out of reset now that we know clk_rtc is running
resetBlock(rp.RESETS_RESET_RTC)
unresetBlockWait(rp.RESETS_RESET_RTC)
// Set up the 1 second divider.
// If rtc_freq is 400 then clkdiv_m1 should be 399
rtcFreq -= 1
// Check the freq is not too big to divide
if rtcFreq > rp.RTC_CLKDIV_M1_CLKDIV_M1_Msk {
panic("can not set RTC divider, clock frequency is too big to divide")
}
// Write divide value
rtc.CLKDIV_M1.Set(rtcFreq)
}
// setTime configures RTC with supplied time, initialises and activates it.
func (rtc *rtcType) setTime(t rtcTime) error {
// Disable RTC and wait while it is still running
rtc.CTRL.Set(0)
for rtc.isActive() {
}
rtc.SETUP_0.Set((uint32(t.Year) << rp.RTC_SETUP_0_YEAR_Pos) |
(uint32(t.Month) << rp.RTC_SETUP_0_MONTH_Pos) |
(uint32(t.Day) << rp.RTC_SETUP_0_DAY_Pos))
rtc.SETUP_1.Set((uint32(t.Dotw) << rp.RTC_SETUP_1_DOTW_Pos) |
(uint32(t.Hour) << rp.RTC_SETUP_1_HOUR_Pos) |
(uint32(t.Min) << rp.RTC_SETUP_1_MIN_Pos) |
(uint32(t.Sec) << rp.RTC_SETUP_1_SEC_Pos))
// Load setup values into RTC clock domain
rtc.CTRL.SetBits(rp.RTC_CTRL_LOAD)
// Enable RTC and wait for it to be running
rtc.CTRL.SetBits(rp.RTC_CTRL_RTC_ENABLE)
for !rtc.isActive() {
}
return nil
}
func (rtc *rtcType) isActive() bool {
return rtc.CTRL.HasBits(rp.RTC_CTRL_RTC_ACTIVE)
}
// setAlarm configures alarm in RTC and arms it.
// The callback is executed in the context of an interrupt handler,
// so regular restructions for this sort of code apply: no blocking, no memory allocation, etc.
func (rtc *rtcType) setAlarm(t rtcTime, callback func()) {
rtc.disableInterruptMatch()
// Clear all match enable bits
rtc.IRQ_SETUP_0.ClearBits(rp.RTC_IRQ_SETUP_0_YEAR_ENA | rp.RTC_IRQ_SETUP_0_MONTH_ENA | rp.RTC_IRQ_SETUP_0_DAY_ENA)
rtc.IRQ_SETUP_1.ClearBits(rp.RTC_IRQ_SETUP_1_DOTW_ENA | rp.RTC_IRQ_SETUP_1_HOUR_ENA | rp.RTC_IRQ_SETUP_1_MIN_ENA | rp.RTC_IRQ_SETUP_1_SEC_ENA)
// Only add to setup if it isn't -1 and set the match enable bits for things we care about
if t.Year >= 0 {
rtc.IRQ_SETUP_0.SetBits(uint32(t.Year) << rp.RTC_SETUP_0_YEAR_Pos)
rtc.IRQ_SETUP_0.SetBits(rp.RTC_IRQ_SETUP_0_YEAR_ENA)
}
if t.Month >= 0 {
rtc.IRQ_SETUP_0.SetBits(uint32(t.Month) << rp.RTC_SETUP_0_MONTH_Pos)
rtc.IRQ_SETUP_0.SetBits(rp.RTC_IRQ_SETUP_0_MONTH_ENA)
}
if t.Day >= 0 {
rtc.IRQ_SETUP_0.SetBits(uint32(t.Day) << rp.RTC_SETUP_0_DAY_Pos)
rtc.IRQ_SETUP_0.SetBits(rp.RTC_IRQ_SETUP_0_DAY_ENA)
}
if t.Dotw >= 0 {
rtc.IRQ_SETUP_1.SetBits(uint32(t.Dotw) << rp.RTC_SETUP_1_DOTW_Pos)
rtc.IRQ_SETUP_1.SetBits(rp.RTC_IRQ_SETUP_1_DOTW_ENA)
}
if t.Hour >= 0 {
rtc.IRQ_SETUP_1.SetBits(uint32(t.Hour) << rp.RTC_SETUP_1_HOUR_Pos)
rtc.IRQ_SETUP_1.SetBits(rp.RTC_IRQ_SETUP_1_HOUR_ENA)
}
if t.Min >= 0 {
rtc.IRQ_SETUP_1.SetBits(uint32(t.Min) << rp.RTC_SETUP_1_MIN_Pos)
rtc.IRQ_SETUP_1.SetBits(rp.RTC_IRQ_SETUP_1_MIN_ENA)
}
if t.Sec >= 0 {
rtc.IRQ_SETUP_1.SetBits(uint32(t.Sec) << rp.RTC_SETUP_1_SEC_Pos)
rtc.IRQ_SETUP_1.SetBits(rp.RTC_IRQ_SETUP_1_SEC_ENA)
}
// Enable the IRQ at the proc
interrupt.New(rp.IRQ_RTC_IRQ, rtcHandleInterrupt).Enable()
// Enable the IRQ at the peri
rtc.INTE.Set(rp.RTC_INTE_RTC)
rtc.enableInterruptMatch()
}
func (rtc *rtcType) enableInterruptMatch() {
// Set matching and wait for it to be enabled
rtc.IRQ_SETUP_0.SetBits(rp.RTC_IRQ_SETUP_0_MATCH_ENA)
for !rtc.IRQ_SETUP_0.HasBits(rp.RTC_IRQ_SETUP_0_MATCH_ACTIVE) {
}
}
func (rtc *rtcType) disableInterruptMatch() {
// Disable matching and wait for it to stop being active
rtc.IRQ_SETUP_0.ClearBits(rp.RTC_IRQ_SETUP_0_MATCH_ENA)
for rtc.IRQ_SETUP_0.HasBits(rp.RTC_IRQ_SETUP_0_MATCH_ACTIVE) {
}
}
func rtcHandleInterrupt(itr interrupt.Interrupt) {
// Always disable the alarm to clear the current IRQ.
// Even if it is a repeatable alarm, we don't want it to keep firing.
// If it matches on a second it can keep firing for that second.
RTC.disableInterruptMatch()
// Call user callback function
if rtcCallback != nil {
rtcCallback()
}
if rtcAlarmRepeats {
// If it is a repeatable alarm, reset time and re-enable the alarm.
RTC.setTime(rtcEpoch)
RTC.enableInterruptMatch()
}
}
+1 -25
View File
@@ -40,9 +40,6 @@ var (
ErrLSBNotSupported = errors.New("SPI LSB unsupported on PL022") ErrLSBNotSupported = errors.New("SPI LSB unsupported on PL022")
ErrSPITimeout = errors.New("SPI timeout") ErrSPITimeout = errors.New("SPI timeout")
ErrSPIBaud = errors.New("SPI baud too low or above 66.5Mhz") ErrSPIBaud = errors.New("SPI baud too low or above 66.5Mhz")
errSPIInvalidSDI = errors.New("invalid SPI SDI pin")
errSPIInvalidSDO = errors.New("invalid SPI SDO pin")
errSPIInvalidSCK = errors.New("invalid SPI SCK pin")
) )
type SPI struct { type SPI struct {
@@ -165,7 +162,7 @@ func (spi SPI) GetBaudRate() uint32 {
// No pin configuration is needed of SCK, SDO and SDI needed after calling Configure. // No pin configuration is needed of SCK, SDO and SDI needed after calling Configure.
func (spi SPI) Configure(config SPIConfig) error { func (spi SPI) Configure(config SPIConfig) error {
const defaultBaud uint32 = 115200 const defaultBaud uint32 = 115200
if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { if config.SCK == 0 {
// set default pins if config zero valued or invalid clock pin supplied. // set default pins if config zero valued or invalid clock pin supplied.
switch spi.Bus { switch spi.Bus {
case rp.SPI0: case rp.SPI0:
@@ -178,27 +175,6 @@ func (spi SPI) Configure(config SPIConfig) error {
config.SDI = SPI1_SDI_PIN config.SDI = SPI1_SDI_PIN
} }
} }
var okSDI, okSDO, okSCK bool
switch spi.Bus {
case rp.SPI0:
okSDI = config.SDI == 0 || config.SDI == 4 || config.SDI == 16
okSDO = config.SDO == 3 || config.SDO == 7 || config.SDO == 19
okSCK = config.SCK == 2 || config.SCK == 6 || config.SCK == 18
case rp.SPI1:
okSDI = config.SDI == 8 || config.SDI == 12 || config.SDI == 28
okSDO = config.SDO == 11 || config.SDO == 15 || config.SDO == 27
okSCK = config.SCK == 10 || config.SCK == 14 || config.SCK == 26
}
switch {
case !okSDI:
return errSPIInvalidSDI
case !okSDO:
return errSPIInvalidSDO
case !okSCK:
return errSPIInvalidSCK
}
if config.DataBits < 4 || config.DataBits > 16 { if config.DataBits < 4 || config.DataBits > 16 {
config.DataBits = 8 config.DataBits = 8
} }
+19
View File
@@ -0,0 +1,19 @@
package runtime
const GOARCH = "mips"
// The bitness of the CPU (e.g. 8, 32, 64).
const TargetBits = 32
const deferExtraRegs = 0
const flag_linux_MAP_ANONYMOUS = 0x800
// Align on the maximum alignment for this platform (double).
func align(ptr uintptr) uintptr {
return (ptr + 7) &^ 7
}
func getCurrentStackPointer() uintptr {
return uintptr(stacksave())
}
View File
+1 -1
View File
@@ -563,7 +563,7 @@ func hashmapInterfaceHash(itf interface{}, seed uintptr) uint32 {
} }
func hashmapInterfacePtrHash(iptr unsafe.Pointer, size uintptr, seed uintptr) uint32 { func hashmapInterfacePtrHash(iptr unsafe.Pointer, size uintptr, seed uintptr) uint32 {
_i := *(*interface{})(iptr) _i := *(*_interface)(iptr)
return hashmapInterfaceHash(_i, seed) return hashmapInterfaceHash(_i, seed)
} }
+1 -1
View File
@@ -14,7 +14,7 @@ const (
flag_PROT_READ = 0x1 flag_PROT_READ = 0x1
flag_PROT_WRITE = 0x2 flag_PROT_WRITE = 0x2
flag_MAP_PRIVATE = 0x2 flag_MAP_PRIVATE = 0x2
flag_MAP_ANONYMOUS = 0x20 flag_MAP_ANONYMOUS = flag_linux_MAP_ANONYMOUS
) )
// Source: https://github.com/torvalds/linux/blob/master/include/uapi/linux/time.h // Source: https://github.com/torvalds/linux/blob/master/include/uapi/linux/time.h
-34
View File
@@ -129,8 +129,6 @@ func main() {
floatcmplx() floatcmplx()
mapgrow() mapgrow()
interfacerehash()
} }
func floatcmplx() { func floatcmplx() {
@@ -276,35 +274,3 @@ func mapgrow() {
} }
println("done") println("done")
} }
type Counter interface {
count() int
}
type counter struct {
i int
}
func (c *counter) count() int {
return c.i
}
func interfacerehash() {
m := make(map[Counter]int)
for i := 0; i < 20; i++ {
c := &counter{i}
m[c] = i
}
var failures int
for k, v := range m {
if got := m[k]; got != v {
println("lookup failure got", got, "want", v)
failures++
}
}
if failures == 0 {
println("no interface lookup failures")
}
}
-1
View File
@@ -80,4 +80,3 @@ tested growing of a map
2 2
2 2
done done
no interface lookup failures
+20
View File
@@ -0,0 +1,20 @@
package transform
import "tinygo.org/x/go-llvm"
// This file implements small transformations on globals (functions and global
// variables) for specific ABIs/architectures.
// ApplyFunctionSections puts every function in a separate section. This makes
// it possible for the linker to remove dead code. It is the equivalent of
// passing -ffunction-sections to a C compiler.
func ApplyFunctionSections(mod llvm.Module) {
llvmFn := mod.FirstFunction()
for !llvmFn.IsNil() {
if !llvmFn.IsDeclaration() && llvmFn.Section() == "" {
name := llvmFn.Name()
llvmFn.SetSection(".text." + name)
}
llvmFn = llvm.NextFunction(llvmFn)
}
}
+15
View File
@@ -0,0 +1,15 @@
package transform_test
import (
"testing"
"github.com/tinygo-org/tinygo/transform"
"tinygo.org/x/go-llvm"
)
func TestApplyFunctionSections(t *testing.T) {
t.Parallel()
testTransform(t, "testdata/globals-function-sections", func(mod llvm.Module) {
transform.ApplyFunctionSections(mod)
})
}
+8
View File
@@ -0,0 +1,8 @@
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7em-none-eabi"
declare void @foo()
define void @bar() {
ret void
}
+8
View File
@@ -0,0 +1,8 @@
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7em-none-eabi"
declare void @foo()
define void @bar() section ".text.bar" {
ret void
}