Compare commits

...

20 Commits

Author SHA1 Message Date
Ayke van Laethem a1e69bbc13 WIP: precise GC 2019-05-14 14:20:39 +02:00
Ayke van Laethem 1b4d71bd3d runtime: refactor garbage collectors 2019-05-14 14:16:24 +02:00
Ayke van Laethem 43b3bb6e83 all: rename garbage collectors
dumb -> leaking:
  make it more clear what this "GC" does: leak everything.
marksweep -> conservative:
  "marksweep" is too generic, use "conservative" to differentiate
  between future garbage collectors: precise marksweep / mark-compact /
  refcounting.
2019-05-14 14:16:16 +02:00
Ayke van Laethem e0cf74e638 avr: use register wrappers that use runtime/volatile.*Uint8 calls
This avoids the //go:volatile pragma on types in Go source code, at
least for AVR targets.
2019-05-14 12:24:01 +02:00
Ayke van Laethem 6f6afb0515 compiler: add //go:inline pragma 2019-05-14 12:24:01 +02:00
Ayke van Laethem 397b90753c compiler: implement volatile operations as compiler builtins
The long term goal is to remove the //go:volatile hack.
2019-05-14 12:24:01 +02:00
Ayke van Laethem 3c2639ad55 compiler: insert nil checks when storing to a pointer
This does increase code size, but it is necessary to avoid undefined
behavior.
2019-05-14 12:24:01 +02:00
Ayke van Laethem 371c468e8e compiler: add debug info for function arguments
This commit adds debug info to function arguments, so that in many cases
you can see them when compiling with less optimizations enabled.
Unfortunately, due to the way Go SSA works, it is hard to preserve them
in many cases.
Local variables are not yet saved.

Also, change the language type to C, to make sure lldb shows function
arguments. The previous language was Modula 3, apparently due to a
off-by-one error somewhere.
2019-05-14 11:18:38 +02:00
Ayke van Laethem 0a40219680 compiler: implement comparing channel values 2019-05-14 11:18:38 +02:00
Ayke van Laethem c981f14e61 compiler: simplify some interface code
No error is produced, so no error needs to be returned. It was missed in
https://github.com/tinygo-org/tinygo/pull/294.

Also, it fixes this smelly code:

    if err != nil {
        return <something>, nil
    }

There could never be an error, so the code was already dead.
2019-05-14 09:59:00 +02:00
Ayke van Laethem 763b9d7d10 runtime: implement growing hashmaps
Add support for growing hashmaps beyond their initial size.
2019-05-14 09:59:00 +02:00
Ayke van Laethem 55fc7b904a compiler,runtime: use the size hint when creating a new map
It defaults to hint/8 number of buckets. This number may be tuned in the
future.
2019-05-14 09:59:00 +02:00
Ayke van Laethem 17c42810d0 compiler: improve hashmaps by avoiding dynamic allocas
By moving all allocas used in hashmap operations to the entry block, the
stack frame remains at a fixed size known at compile time. This avoids
stack overflows when doing map operations in loops and in general
improves code quality: the compiled size of testdata/map.go went from
3776 to 3632 in .text size.
2019-05-14 09:59:00 +02:00
Justin Clift 064d001550 Trivial typo fixes 2019-05-13 17:11:19 +02:00
seph a4cd3bb77c Test for functional argument passing (#336)
* Test for functional argument passing
2019-05-13 14:40:58 +02:00
Anthony Elder 8d3f19bc84 Fix I2C signalStop in readLastByte for Microbit (#344)
* Fix I2C signalStop in readLastByte for Microbit
2019-05-13 14:30:25 +02:00
Ron Evans d90f1947d9 machine/samd21: Initial implementation of I2S hardware interface using Circuit Playground Express
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-05-12 21:51:07 +02:00
Ayke van Laethem 11567c62d4 cgo: refactor; support multiple cgo files in a single package
This is a big commit that does a few things:

  * It moves CGo processing into a separate package. It never really
    belonged in the loader package, and certainly not now that the
    loader package may be refactored into a driver package.
  * It adds support for multiple CGo files (files that import package
    "C") in a single package. Previously, this led to multiple
    definition errors in the Go typecheck phase because certain C
    symbols were defined multiple times in all the files. Now it
    generates a new fake AST that defines these, to avoid multiple
    definition errors.
  * It improves debug info in a few edge cases that are probably not
    relevant outside of bugs in cgo itself.
2019-05-12 10:49:15 +02:00
Ayke van Laethem 4619207f99 cgo: don't crash on import "C" without comment
This doesn't make a lot of sense, but we shouldn't crash on it.
2019-05-12 10:49:15 +02:00
Ayke van Laethem 99587fe073 cgo: add support for #define constants
These are converted to Go constants where possible.
2019-05-12 10:49:15 +02:00
52 changed files with 1773 additions and 537 deletions
+1
View File
@@ -82,6 +82,7 @@ commands:
- run: tinygo build -size short -o test.elf -target=circuitplay-express examples/blinky1 - run: tinygo build -size short -o test.elf -target=circuitplay-express examples/blinky1
- run: tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky1 - run: tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky1
- run: tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky2 - run: tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky2
- run: tinygo build -size short -o test.elf -target=circuitplay-express examples/i2s
- run: tinygo build -o wasm.wasm -target=wasm examples/wasm/export - run: tinygo build -o wasm.wasm -target=wasm examples/wasm/export
- run: tinygo build -o wasm.wasm -target=wasm examples/wasm/main - run: tinygo build -o wasm.wasm -target=wasm examples/wasm/main
test-linux: test-linux:
+1 -1
View File
@@ -32,7 +32,7 @@ CGO_LDFLAGS=-L$(LLVM_BUILDDIR)/lib $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUIL
clean: clean:
@rm -rf build @rm -rf build
FMT_PATHS = ./*.go compiler interp ir loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall FMT_PATHS = ./*.go cgo compiler interp ir loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall
fmt: fmt:
@gofmt -l -w $(FMT_PATHS) @gofmt -l -w $(FMT_PATHS)
fmt-check: fmt-check:
+229 -114
View File
@@ -1,7 +1,15 @@
package loader // Package cgo implements CGo by modifying a loaded AST. It does this by parsing
// the `import "C"` statements found in the source code with libclang and
// generating stub function and global declarations.
//
// There are a few advantages to modifying the AST directly instead of doing CGo
// as a preprocessing step, with the main advantage being that debug information
// is kept intact as much as possible.
package cgo
// This file extracts the `import "C"` statement from the source and modifies // This file extracts the `import "C"` statement from the source and modifies
// the AST for Cgo. It does not use libclang directly (see libclang.go). // the AST for CGo. It does not use libclang directly: see libclang.go for the C
// source file parsing.
import ( import (
"go/ast" "go/ast"
@@ -13,27 +21,38 @@ import (
"golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/go/ast/astutil"
) )
// fileInfo holds all Cgo-related information of a given *ast.File. // cgoPackage holds all CGo-related information of a package.
type fileInfo struct { type cgoPackage struct {
*ast.File generated *ast.File
*Package generatedPos token.Pos
filename string errors []error
functions map[string]*functionInfo dir string
globals map[string]*globalInfo fset *token.FileSet
typedefs map[string]*typedefInfo tokenFiles map[string]*token.File
elaboratedTypes map[string]ast.Expr
importCPos token.Pos
missingSymbols map[string]struct{} missingSymbols map[string]struct{}
constants map[string]constantInfo
functions map[string]*functionInfo
globals map[string]globalInfo
typedefs map[string]*typedefInfo
elaboratedTypes map[string]*elaboratedTypeInfo
} }
// functionInfo stores some information about a Cgo function found by libclang // constantInfo stores some information about a CGo constant found by libclang
// and declared in the Go AST.
type constantInfo struct {
expr *ast.BasicLit
pos token.Pos
}
// functionInfo stores some information about a CGo function found by libclang
// and declared in the AST. // and declared in the AST.
type functionInfo struct { type functionInfo struct {
args []paramInfo args []paramInfo
results *ast.FieldList results *ast.FieldList
pos token.Pos
} }
// paramInfo is a parameter of a Cgo function (see functionInfo). // paramInfo is a parameter of a CGo function (see functionInfo).
type paramInfo struct { type paramInfo struct {
name string name string
typeExpr ast.Expr typeExpr ast.Expr
@@ -42,11 +61,20 @@ type paramInfo struct {
// typedefInfo contains information about a single typedef in C. // typedefInfo contains information about a single typedef in C.
type typedefInfo struct { type typedefInfo struct {
typeExpr ast.Expr typeExpr ast.Expr
pos token.Pos
}
// elaboratedTypeInfo contains some information about an elaborated type
// (struct, union) found in the C AST.
type elaboratedTypeInfo struct {
typeExpr ast.Expr
pos token.Pos
} }
// globalInfo contains information about a declared global variable in C. // globalInfo contains information about a declared global variable in C.
type globalInfo struct { type globalInfo struct {
typeExpr ast.Expr typeExpr ast.Expr
pos token.Pos
} }
// cgoAliases list type aliases between Go and C, for types that are equivalent // cgoAliases list type aliases between Go and C, for types that are equivalent
@@ -63,9 +91,9 @@ var cgoAliases = map[string]string{
"C.uintptr_t": "uintptr", "C.uintptr_t": "uintptr",
} }
// cgoBuiltinAliases are handled specially because they only exist on the Go // builtinAliases are handled specially because they only exist on the Go side
// side of CGo, not on the CGo (they're prefixed with "_Cgo_" there). // of CGo, not on the CGo side (they're prefixed with "_Cgo_" there).
var cgoBuiltinAliases = map[string]struct{}{ var builtinAliases = map[string]struct{}{
"char": struct{}{}, "char": struct{}{},
"schar": struct{}{}, "schar": struct{}{},
"uchar": struct{}{}, "uchar": struct{}{},
@@ -96,27 +124,66 @@ typedef long long _Cgo_longlong;
typedef unsigned long long _Cgo_ulonglong; typedef unsigned long long _Cgo_ulonglong;
` `
// processCgo extracts the `import "C"` statement from the AST, parses the // Process extracts `import "C"` statements from the AST, parses the comment
// comment with libclang, and modifies the AST to use this information. // with libclang, and modifies the AST to use this information. It returns a
func (p *Package) processCgo(filename string, f *ast.File, cflags []string) []error { // newly created *ast.File that should be added to the list of to-be-parsed
info := &fileInfo{ // files. If there is one or more error, it returns these in the []error slice
File: f, // but still modifies the AST.
Package: p, func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []error) {
filename: filename, p := &cgoPackage{
functions: map[string]*functionInfo{}, dir: dir,
globals: map[string]*globalInfo{}, fset: fset,
typedefs: map[string]*typedefInfo{}, tokenFiles: map[string]*token.File{},
elaboratedTypes: map[string]ast.Expr{},
missingSymbols: map[string]struct{}{}, missingSymbols: map[string]struct{}{},
constants: map[string]constantInfo{},
functions: map[string]*functionInfo{},
globals: map[string]globalInfo{},
typedefs: map[string]*typedefInfo{},
elaboratedTypes: map[string]*elaboratedTypeInfo{},
}
// Add a new location for the following file.
generatedTokenPos := p.fset.AddFile(dir+"/!cgo.go", -1, 0)
generatedTokenPos.SetLines([]int{0})
p.generatedPos = generatedTokenPos.Pos(0)
// Construct a new in-memory AST for CGo declarations of this package.
unsafeImport := &ast.ImportSpec{
Path: &ast.BasicLit{
ValuePos: p.generatedPos,
Kind: token.STRING,
Value: "\"unsafe\"",
},
EndPos: p.generatedPos,
}
p.generated = &ast.File{
Package: p.generatedPos,
Name: &ast.Ident{
NamePos: p.generatedPos,
Name: files[0].Name.Name,
},
Decls: []ast.Decl{
&ast.GenDecl{
TokPos: p.generatedPos,
Tok: token.IMPORT,
Specs: []ast.Spec{
unsafeImport,
},
},
},
Imports: []*ast.ImportSpec{unsafeImport},
} }
// Find all C.* symbols. // Find all C.* symbols.
f = astutil.Apply(f, info.findMissingCGoNames, nil).(*ast.File) for _, f := range files {
for name := range cgoBuiltinAliases { astutil.Apply(f, p.findMissingCGoNames, nil)
info.missingSymbols["_Cgo_"+name] = struct{}{} }
for name := range builtinAliases {
p.missingSymbols["_Cgo_"+name] = struct{}{}
} }
// Find `import "C"` statements in the file. // Find `import "C"` statements in the file.
for _, f := range files {
for i := 0; i < len(f.Decls); i++ { for i := 0; i < len(f.Decls); i++ {
decl := f.Decls[i] decl := f.Decls[i]
genDecl, ok := decl.(*ast.GenDecl) genDecl, ok := decl.(*ast.GenDecl)
@@ -139,15 +206,12 @@ func (p *Package) processCgo(filename string, f *ast.File, cflags []string) []er
} }
cgoComment := genDecl.Doc.Text() cgoComment := genDecl.Doc.Text()
// Stored for later use by generated functions, to use a somewhat sane pos := genDecl.Pos()
// source location. if genDecl.Doc != nil {
info.importCPos = spec.Path.ValuePos pos = genDecl.Doc.Pos()
pos := info.fset.PositionFor(genDecl.Doc.Pos(), true)
errs := info.parseFragment(cgoComment+cgoTypes, cflags, pos.Filename, pos.Line)
if errs != nil {
return errs
} }
position := fset.PositionFor(pos, true)
p.parseFragment(cgoComment+cgoTypes, cflags, position.Filename, position.Line)
// Remove this import declaration. // Remove this import declaration.
f.Decls = append(f.Decls[:i], f.Decls[i+1:]...) f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
@@ -155,44 +219,51 @@ func (p *Package) processCgo(filename string, f *ast.File, cflags []string) []er
} }
// Print the AST, for debugging. // Print the AST, for debugging.
//ast.Print(p.fset, f) //ast.Print(fset, f)
}
// Declare functions found by libclang. // Declare functions found by libclang.
info.addFuncDecls() p.addFuncDecls()
// Declare stub function pointer values found by libclang. // Declare stub function pointer values found by libclang.
info.addFuncPtrDecls() p.addFuncPtrDecls()
// Declare globals found by libclang. // Declare globals found by libclang.
info.addVarDecls() p.addConstDecls()
// Declare globals found by libclang.
p.addVarDecls()
// Forward C types to Go types (like C.uint32_t -> uint32). // Forward C types to Go types (like C.uint32_t -> uint32).
info.addTypeAliases() p.addTypeAliases()
// Add type declarations for C types, declared using typedef in C. // Add type declarations for C types, declared using typedef in C.
info.addTypedefs() p.addTypedefs()
// Add elaborated types for C structs and unions. // Add elaborated types for C structs and unions.
info.addElaboratedTypes() p.addElaboratedTypes()
// Patch the AST to use the declared types and functions. // Patch the AST to use the declared types and functions.
f = astutil.Apply(f, info.walker, nil).(*ast.File) for _, f := range files {
astutil.Apply(f, p.walker, nil)
}
return nil // Print the newly generated in-memory AST, for debugging.
//ast.Print(fset, p.generated)
return p.generated, p.errors
} }
// addFuncDecls adds the C function declarations found by libclang in the // addFuncDecls adds the C function declarations found by libclang in the
// comment above the `import "C"` statement. // comment above the `import "C"` statement.
func (info *fileInfo) addFuncDecls() { func (p *cgoPackage) addFuncDecls() {
// TODO: replace all uses of importCPos with the real locations from names := make([]string, 0, len(p.functions))
// libclang. for name := range p.functions {
names := make([]string, 0, len(info.functions))
for name := range info.functions {
names = append(names, name) names = append(names, name)
} }
sort.Strings(names) sort.Strings(names)
for _, name := range names { for _, name := range names {
fn := info.functions[name] fn := p.functions[name]
obj := &ast.Object{ obj := &ast.Object{
Kind: ast.Fun, Kind: ast.Fun,
Name: "C." + name, Name: "C." + name,
@@ -200,16 +271,16 @@ func (info *fileInfo) addFuncDecls() {
args := make([]*ast.Field, len(fn.args)) args := make([]*ast.Field, len(fn.args))
decl := &ast.FuncDecl{ decl := &ast.FuncDecl{
Name: &ast.Ident{ Name: &ast.Ident{
NamePos: info.importCPos, NamePos: fn.pos,
Name: "C." + name, Name: "C." + name,
Obj: obj, Obj: obj,
}, },
Type: &ast.FuncType{ Type: &ast.FuncType{
Func: info.importCPos, Func: fn.pos,
Params: &ast.FieldList{ Params: &ast.FieldList{
Opening: info.importCPos, Opening: fn.pos,
List: args, List: args,
Closing: info.importCPos, Closing: fn.pos,
}, },
Results: fn.results, Results: fn.results,
}, },
@@ -219,7 +290,7 @@ func (info *fileInfo) addFuncDecls() {
args[i] = &ast.Field{ args[i] = &ast.Field{
Names: []*ast.Ident{ Names: []*ast.Ident{
&ast.Ident{ &ast.Ident{
NamePos: info.importCPos, NamePos: fn.pos,
Name: arg.name, Name: arg.name,
Obj: &ast.Object{ Obj: &ast.Object{
Kind: ast.Var, Kind: ast.Var,
@@ -231,7 +302,7 @@ func (info *fileInfo) addFuncDecls() {
Type: arg.typeExpr, Type: arg.typeExpr,
} }
} }
info.Decls = append(info.Decls, decl) p.generated.Decls = append(p.generated.Decls, decl)
} }
} }
@@ -244,39 +315,40 @@ func (info *fileInfo) addFuncDecls() {
// C.mul unsafe.Pointer // C.mul unsafe.Pointer
// // ... // // ...
// ) // )
func (info *fileInfo) addFuncPtrDecls() { func (p *cgoPackage) addFuncPtrDecls() {
if len(info.functions) == 0 { if len(p.functions) == 0 {
return return
} }
gen := &ast.GenDecl{ gen := &ast.GenDecl{
TokPos: info.importCPos, TokPos: token.NoPos,
Tok: token.VAR, Tok: token.VAR,
Lparen: info.importCPos, Lparen: token.NoPos,
Rparen: info.importCPos, Rparen: token.NoPos,
} }
names := make([]string, 0, len(info.functions)) names := make([]string, 0, len(p.functions))
for name := range info.functions { for name := range p.functions {
names = append(names, name) names = append(names, name)
} }
sort.Strings(names) sort.Strings(names)
for _, name := range names { for _, name := range names {
fn := p.functions[name]
obj := &ast.Object{ obj := &ast.Object{
Kind: ast.Typ, Kind: ast.Typ,
Name: "C." + name + "$funcaddr", Name: "C." + name + "$funcaddr",
} }
valueSpec := &ast.ValueSpec{ valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{&ast.Ident{ Names: []*ast.Ident{&ast.Ident{
NamePos: info.importCPos, NamePos: fn.pos,
Name: "C." + name + "$funcaddr", Name: "C." + name + "$funcaddr",
Obj: obj, Obj: obj,
}}, }},
Type: &ast.SelectorExpr{ Type: &ast.SelectorExpr{
X: &ast.Ident{ X: &ast.Ident{
NamePos: info.importCPos, NamePos: fn.pos,
Name: "unsafe", Name: "unsafe",
}, },
Sel: &ast.Ident{ Sel: &ast.Ident{
NamePos: info.importCPos, NamePos: fn.pos,
Name: "Pointer", Name: "Pointer",
}, },
}, },
@@ -284,7 +356,50 @@ func (info *fileInfo) addFuncPtrDecls() {
obj.Decl = valueSpec obj.Decl = valueSpec
gen.Specs = append(gen.Specs, valueSpec) gen.Specs = append(gen.Specs, valueSpec)
} }
info.Decls = append(info.Decls, gen) p.generated.Decls = append(p.generated.Decls, gen)
}
// addConstDecls declares external C constants in the Go source.
// It adds code like the following to the AST:
//
// const (
// C.CONST_INT = 5
// C.CONST_FLOAT = 5.8
// // ...
// )
func (p *cgoPackage) addConstDecls() {
if len(p.constants) == 0 {
return
}
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.CONST,
Lparen: token.NoPos,
Rparen: token.NoPos,
}
names := make([]string, 0, len(p.constants))
for name := range p.constants {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
constVal := p.constants[name]
obj := &ast.Object{
Kind: ast.Con,
Name: "C." + name,
}
valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{&ast.Ident{
NamePos: constVal.pos,
Name: "C." + name,
Obj: obj,
}},
Values: []ast.Expr{constVal.expr},
}
obj.Decl = valueSpec
gen.Specs = append(gen.Specs, valueSpec)
}
p.generated.Decls = append(p.generated.Decls, gen)
} }
// addVarDecls declares external C globals in the Go source. // addVarDecls declares external C globals in the Go source.
@@ -295,30 +410,30 @@ func (info *fileInfo) addFuncPtrDecls() {
// C.globalBool bool // C.globalBool bool
// // ... // // ...
// ) // )
func (info *fileInfo) addVarDecls() { func (p *cgoPackage) addVarDecls() {
if len(info.globals) == 0 { if len(p.globals) == 0 {
return return
} }
gen := &ast.GenDecl{ gen := &ast.GenDecl{
TokPos: info.importCPos, TokPos: token.NoPos,
Tok: token.VAR, Tok: token.VAR,
Lparen: info.importCPos, Lparen: token.NoPos,
Rparen: info.importCPos, Rparen: token.NoPos,
} }
names := make([]string, 0, len(info.globals)) names := make([]string, 0, len(p.globals))
for name := range info.globals { for name := range p.globals {
names = append(names, name) names = append(names, name)
} }
sort.Strings(names) sort.Strings(names)
for _, name := range names { for _, name := range names {
global := info.globals[name] global := p.globals[name]
obj := &ast.Object{ obj := &ast.Object{
Kind: ast.Typ, Kind: ast.Var,
Name: "C." + name, Name: "C." + name,
} }
valueSpec := &ast.ValueSpec{ valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{&ast.Ident{ Names: []*ast.Ident{&ast.Ident{
NamePos: info.importCPos, NamePos: global.pos,
Name: "C." + name, Name: "C." + name,
Obj: obj, Obj: obj,
}}, }},
@@ -327,7 +442,7 @@ func (info *fileInfo) addVarDecls() {
obj.Decl = valueSpec obj.Decl = valueSpec
gen.Specs = append(gen.Specs, valueSpec) gen.Specs = append(gen.Specs, valueSpec)
} }
info.Decls = append(info.Decls, gen) p.generated.Decls = append(p.generated.Decls, gen)
} }
// addTypeAliases aliases some built-in Go types with their equivalent C types. // addTypeAliases aliases some built-in Go types with their equivalent C types.
@@ -338,17 +453,17 @@ func (info *fileInfo) addVarDecls() {
// C.int16_t = int16 // C.int16_t = int16
// // ... // // ...
// ) // )
func (info *fileInfo) addTypeAliases() { func (p *cgoPackage) addTypeAliases() {
aliasKeys := make([]string, 0, len(cgoAliases)) aliasKeys := make([]string, 0, len(cgoAliases))
for key := range cgoAliases { for key := range cgoAliases {
aliasKeys = append(aliasKeys, key) aliasKeys = append(aliasKeys, key)
} }
sort.Strings(aliasKeys) sort.Strings(aliasKeys)
gen := &ast.GenDecl{ gen := &ast.GenDecl{
TokPos: info.importCPos, TokPos: token.NoPos,
Tok: token.TYPE, Tok: token.TYPE,
Lparen: info.importCPos, Lparen: token.NoPos,
Rparen: info.importCPos, Rparen: token.NoPos,
} }
for _, typeName := range aliasKeys { for _, typeName := range aliasKeys {
goTypeName := cgoAliases[typeName] goTypeName := cgoAliases[typeName]
@@ -358,37 +473,37 @@ func (info *fileInfo) addTypeAliases() {
} }
typeSpec := &ast.TypeSpec{ typeSpec := &ast.TypeSpec{
Name: &ast.Ident{ Name: &ast.Ident{
NamePos: info.importCPos, NamePos: token.NoPos,
Name: typeName, Name: typeName,
Obj: obj, Obj: obj,
}, },
Assign: info.importCPos, Assign: p.generatedPos,
Type: &ast.Ident{ Type: &ast.Ident{
NamePos: info.importCPos, NamePos: token.NoPos,
Name: goTypeName, Name: goTypeName,
}, },
} }
obj.Decl = typeSpec obj.Decl = typeSpec
gen.Specs = append(gen.Specs, typeSpec) gen.Specs = append(gen.Specs, typeSpec)
} }
info.Decls = append(info.Decls, gen) p.generated.Decls = append(p.generated.Decls, gen)
} }
func (info *fileInfo) addTypedefs() { func (p *cgoPackage) addTypedefs() {
if len(info.typedefs) == 0 { if len(p.typedefs) == 0 {
return return
} }
gen := &ast.GenDecl{ gen := &ast.GenDecl{
TokPos: info.importCPos, TokPos: token.NoPos,
Tok: token.TYPE, Tok: token.TYPE,
} }
names := make([]string, 0, len(info.typedefs)) names := make([]string, 0, len(p.typedefs))
for name := range info.typedefs { for name := range p.typedefs {
names = append(names, name) names = append(names, name)
} }
sort.Strings(names) sort.Strings(names)
for _, name := range names { for _, name := range names {
typedef := info.typedefs[name] typedef := p.typedefs[name]
typeName := "C." + name typeName := "C." + name
isAlias := true isAlias := true
if strings.HasPrefix(name, "_Cgo_") { if strings.HasPrefix(name, "_Cgo_") {
@@ -405,19 +520,19 @@ func (info *fileInfo) addTypedefs() {
} }
typeSpec := &ast.TypeSpec{ typeSpec := &ast.TypeSpec{
Name: &ast.Ident{ Name: &ast.Ident{
NamePos: info.importCPos, NamePos: typedef.pos,
Name: typeName, Name: typeName,
Obj: obj, Obj: obj,
}, },
Type: typedef.typeExpr, Type: typedef.typeExpr,
} }
if isAlias { if isAlias {
typeSpec.Assign = info.importCPos typeSpec.Assign = typedef.pos
} }
obj.Decl = typeSpec obj.Decl = typeSpec
gen.Specs = append(gen.Specs, typeSpec) gen.Specs = append(gen.Specs, typeSpec)
} }
info.Decls = append(info.Decls, gen) p.generated.Decls = append(p.generated.Decls, gen)
} }
// addElaboratedTypes adds C elaborated types as aliases. These are the "struct // addElaboratedTypes adds C elaborated types as aliases. These are the "struct
@@ -425,21 +540,21 @@ func (info *fileInfo) addTypedefs() {
// //
// See also: // See also:
// https://en.cppreference.com/w/cpp/language/elaborated_type_specifier // https://en.cppreference.com/w/cpp/language/elaborated_type_specifier
func (info *fileInfo) addElaboratedTypes() { func (p *cgoPackage) addElaboratedTypes() {
if len(info.elaboratedTypes) == 0 { if len(p.elaboratedTypes) == 0 {
return return
} }
gen := &ast.GenDecl{ gen := &ast.GenDecl{
TokPos: info.importCPos, TokPos: token.NoPos,
Tok: token.TYPE, Tok: token.TYPE,
} }
names := make([]string, 0, len(info.elaboratedTypes)) names := make([]string, 0, len(p.elaboratedTypes))
for name := range info.elaboratedTypes { for name := range p.elaboratedTypes {
names = append(names, name) names = append(names, name)
} }
sort.Strings(names) sort.Strings(names)
for _, name := range names { for _, name := range names {
typ := info.elaboratedTypes[name] typ := p.elaboratedTypes[name]
typeName := "C." + name typeName := "C." + name
obj := &ast.Object{ obj := &ast.Object{
Kind: ast.Typ, Kind: ast.Typ,
@@ -447,22 +562,22 @@ func (info *fileInfo) addElaboratedTypes() {
} }
typeSpec := &ast.TypeSpec{ typeSpec := &ast.TypeSpec{
Name: &ast.Ident{ Name: &ast.Ident{
NamePos: info.importCPos, NamePos: typ.pos,
Name: typeName, Name: typeName,
Obj: obj, Obj: obj,
}, },
Type: typ, Type: typ.typeExpr,
} }
obj.Decl = typeSpec obj.Decl = typeSpec
gen.Specs = append(gen.Specs, typeSpec) gen.Specs = append(gen.Specs, typeSpec)
} }
info.Decls = append(info.Decls, gen) p.generated.Decls = append(p.generated.Decls, gen)
} }
// findMissingCGoNames traverses the AST and finds all C.something names. Only // findMissingCGoNames traverses the AST and finds all C.something names. Only
// these symbols are extracted from the parsed C AST and converted to the Go // these symbols are extracted from the parsed C AST and converted to the Go
// equivalent. // equivalent.
func (info *fileInfo) findMissingCGoNames(cursor *astutil.Cursor) bool { func (p *cgoPackage) findMissingCGoNames(cursor *astutil.Cursor) bool {
switch node := cursor.Node().(type) { switch node := cursor.Node().(type) {
case *ast.SelectorExpr: case *ast.SelectorExpr:
x, ok := node.X.(*ast.Ident) x, ok := node.X.(*ast.Ident)
@@ -471,10 +586,10 @@ func (info *fileInfo) findMissingCGoNames(cursor *astutil.Cursor) bool {
} }
if x.Name == "C" { if x.Name == "C" {
name := node.Sel.Name name := node.Sel.Name
if _, ok := cgoBuiltinAliases[name]; ok { if _, ok := builtinAliases[name]; ok {
name = "_Cgo_" + name name = "_Cgo_" + name
} }
info.missingSymbols[name] = struct{}{} p.missingSymbols[name] = struct{}{}
} }
} }
return true return true
@@ -484,7 +599,7 @@ func (info *fileInfo) findMissingCGoNames(cursor *astutil.Cursor) bool {
// expressions. Such expressions are impossible to write in Go (a dot cannot be // expressions. Such expressions are impossible to write in Go (a dot cannot be
// used in the middle of a name) so in practice all C identifiers live in a // used in the middle of a name) so in practice all C identifiers live in a
// separate namespace (no _Cgo_ hacks like in gc). // separate namespace (no _Cgo_ hacks like in gc).
func (info *fileInfo) walker(cursor *astutil.Cursor) bool { func (p *cgoPackage) walker(cursor *astutil.Cursor) bool {
switch node := cursor.Node().(type) { switch node := cursor.Node().(type) {
case *ast.CallExpr: case *ast.CallExpr:
fun, ok := node.Fun.(*ast.SelectorExpr) fun, ok := node.Fun.(*ast.SelectorExpr)
@@ -495,7 +610,7 @@ func (info *fileInfo) walker(cursor *astutil.Cursor) bool {
if !ok { if !ok {
return true return true
} }
if _, ok := info.functions[fun.Sel.Name]; ok && x.Name == "C" { if _, ok := p.functions[fun.Sel.Name]; ok && x.Name == "C" {
node.Fun = &ast.Ident{ node.Fun = &ast.Ident{
NamePos: x.NamePos, NamePos: x.NamePos,
Name: "C." + fun.Sel.Name, Name: "C." + fun.Sel.Name,
@@ -508,7 +623,7 @@ func (info *fileInfo) walker(cursor *astutil.Cursor) bool {
} }
if x.Name == "C" { if x.Name == "C" {
name := "C." + node.Sel.Name name := "C." + node.Sel.Name
if _, ok := info.functions[node.Sel.Name]; ok { if _, ok := p.functions[node.Sel.Name]; ok {
name += "$funcaddr" name += "$funcaddr"
} }
cursor.Replace(&ast.Ident{ cursor.Replace(&ast.Ident{
+141 -53
View File
@@ -1,4 +1,4 @@
package loader package cgo
// This file parses a fragment of C with libclang and stores the result for AST // This file parses a fragment of C with libclang and stores the result for AST
// modification. It does not touch the AST itself. // modification. It does not touch the AST itself.
@@ -47,6 +47,7 @@ CXType tinygo_clang_getCursorResultType(GoCXCursor c);
int tinygo_clang_Cursor_getNumArguments(GoCXCursor c); int tinygo_clang_Cursor_getNumArguments(GoCXCursor c);
GoCXCursor tinygo_clang_Cursor_getArgument(GoCXCursor c, unsigned i); GoCXCursor tinygo_clang_Cursor_getArgument(GoCXCursor c, unsigned i);
CXSourceLocation tinygo_clang_getCursorLocation(GoCXCursor c); CXSourceLocation tinygo_clang_getCursorLocation(GoCXCursor c);
CXSourceRange tinygo_clang_getCursorExtent(GoCXCursor c);
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(GoCXCursor c); CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(GoCXCursor c);
int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
@@ -54,8 +55,8 @@ int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData cl
*/ */
import "C" import "C"
// refMap stores references to types, used for clang_visitChildren. // storedRefs stores references to types, used for clang_visitChildren.
var refMap RefMap var storedRefs refMap
var diagnosticSeverity = [...]string{ var diagnosticSeverity = [...]string{
C.CXDiagnostic_Ignored: "ignored", C.CXDiagnostic_Ignored: "ignored",
@@ -65,7 +66,7 @@ var diagnosticSeverity = [...]string{
C.CXDiagnostic_Fatal: "fatal", C.CXDiagnostic_Fatal: "fatal",
} }
func (info *fileInfo) parseFragment(fragment string, cflags []string, posFilename string, posLine int) []error { func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename string, posLine int) {
index := C.clang_createIndex(0, 0) index := C.clang_createIndex(0, 0)
defer C.clang_disposeIndex(index) defer C.clang_disposeIndex(index)
@@ -101,7 +102,7 @@ func (info *fileInfo) parseFragment(fragment string, cflags []string, posFilenam
filenameC, filenameC,
(**C.char)(cmdargsC), C.int(len(cflags)), // command line args (**C.char)(cmdargsC), C.int(len(cflags)), // command line args
&unsavedFile, 1, // unsaved files &unsavedFile, 1, // unsaved files
C.CXTranslationUnit_None, C.CXTranslationUnit_DetailedPreprocessingRecord,
&unit) &unit)
if errCode != 0 { if errCode != 0 {
panic("loader: failed to parse source with libclang") panic("loader: failed to parse source with libclang")
@@ -109,7 +110,6 @@ func (info *fileInfo) parseFragment(fragment string, cflags []string, posFilenam
defer C.clang_disposeTranslationUnit(unit) defer C.clang_disposeTranslationUnit(unit)
if numDiagnostics := int(C.clang_getNumDiagnostics(unit)); numDiagnostics != 0 { if numDiagnostics := int(C.clang_getNumDiagnostics(unit)); numDiagnostics != 0 {
errs := []error{}
addDiagnostic := func(diagnostic C.CXDiagnostic) { addDiagnostic := func(diagnostic C.CXDiagnostic) {
spelling := getString(C.clang_getDiagnosticSpelling(diagnostic)) spelling := getString(C.clang_getDiagnosticSpelling(diagnostic))
severity := diagnosticSeverity[C.clang_getDiagnosticSeverity(diagnostic)] severity := diagnosticSeverity[C.clang_getDiagnosticSeverity(diagnostic)]
@@ -121,12 +121,12 @@ func (info *fileInfo) parseFragment(fragment string, cflags []string, posFilenam
filename := getString(libclangFilename) filename := getString(libclangFilename)
if filepath.IsAbs(filename) { if filepath.IsAbs(filename) {
// Relative paths for readability, like other Go parser errors. // Relative paths for readability, like other Go parser errors.
relpath, err := filepath.Rel(info.Program.Dir, filename) relpath, err := filepath.Rel(p.dir, filename)
if err == nil { if err == nil {
filename = relpath filename = relpath
} }
} }
errs = append(errs, &scanner.Error{ p.errors = append(p.errors, &scanner.Error{
Pos: token.Position{ Pos: token.Position{
Filename: filename, Filename: filename,
Offset: 0, // not provided by clang_getPresumedLocation Offset: 0, // not provided by clang_getPresumedLocation
@@ -146,26 +146,24 @@ func (info *fileInfo) parseFragment(fragment string, cflags []string, posFilenam
addDiagnostic(C.clang_getDiagnosticInSet(diagnostics, C.uint(j))) addDiagnostic(C.clang_getDiagnosticInSet(diagnostics, C.uint(j)))
} }
} }
return errs return
} }
ref := refMap.Put(info) ref := storedRefs.Put(p)
defer refMap.Remove(ref) defer storedRefs.Remove(ref)
cursor := C.tinygo_clang_getTranslationUnitCursor(unit) cursor := C.tinygo_clang_getTranslationUnitCursor(unit)
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_globals_visitor), C.CXClientData(ref)) C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_globals_visitor), C.CXClientData(ref))
return nil
} }
//export tinygo_clang_globals_visitor //export tinygo_clang_globals_visitor
func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int { func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
info := refMap.Get(unsafe.Pointer(client_data)).(*fileInfo) p := storedRefs.Get(unsafe.Pointer(client_data)).(*cgoPackage)
kind := C.tinygo_clang_getCursorKind(c) kind := C.tinygo_clang_getCursorKind(c)
pos := info.getCursorPosition(c) pos := p.getCursorPosition(c)
switch kind { switch kind {
case C.CXCursor_FunctionDecl: case C.CXCursor_FunctionDecl:
name := getString(C.tinygo_clang_getCursorSpelling(c)) name := getString(C.tinygo_clang_getCursorSpelling(c))
if _, required := info.missingSymbols[name]; !required { if _, required := p.missingSymbols[name]; !required {
return C.CXChildVisit_Continue return C.CXChildVisit_Continue
} }
cursorType := C.tinygo_clang_getCursorType(c) cursorType := C.tinygo_clang_getCursorType(c)
@@ -173,8 +171,10 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
return C.CXChildVisit_Continue // not supported return C.CXChildVisit_Continue // not supported
} }
numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c)) numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c))
fn := &functionInfo{} fn := &functionInfo{
info.functions[name] = fn pos: pos,
}
p.functions[name] = fn
for i := 0; i < numArgs; i++ { for i := 0; i < numArgs; i++ {
arg := C.tinygo_clang_Cursor_getArgument(c, C.uint(i)) arg := C.tinygo_clang_Cursor_getArgument(c, C.uint(i))
argName := getString(C.tinygo_clang_getCursorSpelling(arg)) argName := getString(C.tinygo_clang_getCursorSpelling(arg))
@@ -184,7 +184,7 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
} }
fn.args = append(fn.args, paramInfo{ fn.args = append(fn.args, paramInfo{
name: argName, name: argName,
typeExpr: info.makeASTType(argType, pos), typeExpr: p.makeASTType(argType, pos),
}) })
} }
resultType := C.tinygo_clang_getCursorResultType(c) resultType := C.tinygo_clang_getCursorResultType(c)
@@ -192,7 +192,7 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
fn.results = &ast.FieldList{ fn.results = &ast.FieldList{
List: []*ast.Field{ List: []*ast.Field{
&ast.Field{ &ast.Field{
Type: info.makeASTType(resultType, pos), Type: p.makeASTType(resultType, pos),
}, },
}, },
} }
@@ -200,25 +200,106 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
case C.CXCursor_StructDecl: case C.CXCursor_StructDecl:
typ := C.tinygo_clang_getCursorType(c) typ := C.tinygo_clang_getCursorType(c)
name := getString(C.tinygo_clang_getCursorSpelling(c)) name := getString(C.tinygo_clang_getCursorSpelling(c))
if _, required := info.missingSymbols["struct_"+name]; !required { if _, required := p.missingSymbols["struct_"+name]; !required {
return C.CXChildVisit_Continue return C.CXChildVisit_Continue
} }
info.makeASTType(typ, pos) p.makeASTType(typ, pos)
case C.CXCursor_TypedefDecl: case C.CXCursor_TypedefDecl:
typedefType := C.tinygo_clang_getCursorType(c) typedefType := C.tinygo_clang_getCursorType(c)
name := getString(C.clang_getTypedefName(typedefType)) name := getString(C.clang_getTypedefName(typedefType))
if _, required := info.missingSymbols[name]; !required { if _, required := p.missingSymbols[name]; !required {
return C.CXChildVisit_Continue return C.CXChildVisit_Continue
} }
info.makeASTType(typedefType, pos) p.makeASTType(typedefType, pos)
case C.CXCursor_VarDecl: case C.CXCursor_VarDecl:
name := getString(C.tinygo_clang_getCursorSpelling(c)) name := getString(C.tinygo_clang_getCursorSpelling(c))
if _, required := info.missingSymbols[name]; !required { if _, required := p.missingSymbols[name]; !required {
return C.CXChildVisit_Continue return C.CXChildVisit_Continue
} }
cursorType := C.tinygo_clang_getCursorType(c) cursorType := C.tinygo_clang_getCursorType(c)
info.globals[name] = &globalInfo{ p.globals[name] = globalInfo{
typeExpr: info.makeASTType(cursorType, pos), typeExpr: p.makeASTType(cursorType, pos),
pos: pos,
}
case C.CXCursor_MacroDefinition:
name := getString(C.tinygo_clang_getCursorSpelling(c))
if _, required := p.missingSymbols[name]; !required {
return C.CXChildVisit_Continue
}
sourceRange := C.tinygo_clang_getCursorExtent(c)
start := C.clang_getRangeStart(sourceRange)
end := C.clang_getRangeEnd(sourceRange)
var file, endFile C.CXFile
var startOffset, endOffset C.unsigned
C.clang_getExpansionLocation(start, &file, nil, nil, &startOffset)
if file == nil {
panic("could not find file where macro is defined")
}
C.clang_getExpansionLocation(end, &endFile, nil, nil, &endOffset)
if file != endFile {
panic("expected start and end location of a #define to be in the same file")
}
if startOffset > endOffset {
panic("startOffset > endOffset")
}
// read file contents and extract the relevant byte range
tu := C.tinygo_clang_Cursor_getTranslationUnit(c)
var size C.size_t
sourcePtr := C.clang_getFileContents(tu, file, &size)
if endOffset >= C.uint(size) {
panic("endOffset lies after end of file")
}
source := string(((*[1 << 28]byte)(unsafe.Pointer(sourcePtr)))[startOffset:endOffset:endOffset])
if !strings.HasPrefix(source, name) {
panic(fmt.Sprintf("expected #define value to start with %#v, got %#v", name, source))
}
value := strings.TrimSpace(source[len(name):])
for len(value) != 0 && value[0] == '(' && value[len(value)-1] == ')' {
value = strings.TrimSpace(value[1 : len(value)-1])
}
if len(value) == 0 {
// Pretend it doesn't exist at all.
return C.CXChildVisit_Continue
}
// For information about integer literals:
// https://en.cppreference.com/w/cpp/language/integer_literal
if value[0] == '"' {
// string constant
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.STRING, value}, pos}
return C.CXChildVisit_Continue
}
if value[0] == '\'' {
// char constant
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.CHAR, value}, pos}
return C.CXChildVisit_Continue
}
// assume it's a number (int or float)
value = strings.Replace(value, "'", "", -1) // remove ' chars
value = strings.TrimRight(value, "lu") // remove llu suffixes etc.
// find the first non-number
nonnum := byte(0)
for i := 0; i < len(value); i++ {
if value[i] < '0' || value[i] > '9' {
nonnum = value[i]
break
}
}
// determine number type based on the first non-number
switch nonnum {
case 0:
// no non-number found, must be an integer
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.INT, value}, pos}
case 'x', 'X':
// hex integer constant
// TODO: may also be a floating point number per C++17.
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.INT, value}, pos}
case '.', 'e':
// float constant
value = strings.TrimRight(value, "fFlL")
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.FLOAT, value}, pos}
default:
// unknown type, ignore
} }
} }
return C.CXChildVisit_Continue return C.CXChildVisit_Continue
@@ -234,7 +315,7 @@ func getString(clangString C.CXString) (s string) {
// getCursorPosition returns a usable token.Pos from a libclang cursor. If the // getCursorPosition returns a usable token.Pos from a libclang cursor. If the
// file for this cursor has not been seen before, it is read from libclang // file for this cursor has not been seen before, it is read from libclang
// (which already has the file in memory) and added to the token.FileSet. // (which already has the file in memory) and added to the token.FileSet.
func (info *fileInfo) getCursorPosition(cursor C.GoCXCursor) token.Pos { func (p *cgoPackage) getCursorPosition(cursor C.GoCXCursor) token.Pos {
location := C.tinygo_clang_getCursorLocation(cursor) location := C.tinygo_clang_getCursorLocation(cursor)
var file C.CXFile var file C.CXFile
var line C.unsigned var line C.unsigned
@@ -246,7 +327,7 @@ func (info *fileInfo) getCursorPosition(cursor C.GoCXCursor) token.Pos {
return token.NoPos return token.NoPos
} }
filename := getString(C.clang_getFileName(file)) filename := getString(C.clang_getFileName(file))
if _, ok := info.tokenFiles[filename]; !ok { if _, ok := p.tokenFiles[filename]; !ok {
// File has not been seen before in this package, add line information // File has not been seen before in this package, add line information
// now by reading the file from libclang. // now by reading the file from libclang.
tu := C.tinygo_clang_Cursor_getTranslationUnit(cursor) tu := C.tinygo_clang_Cursor_getTranslationUnit(cursor)
@@ -259,16 +340,16 @@ func (info *fileInfo) getCursorPosition(cursor C.GoCXCursor) token.Pos {
lines = append(lines, i+1) lines = append(lines, i+1)
} }
} }
f := info.fset.AddFile(filename, -1, int(size)) f := p.fset.AddFile(filename, -1, int(size))
f.SetLines(lines) f.SetLines(lines)
info.tokenFiles[filename] = f p.tokenFiles[filename] = f
} }
return info.tokenFiles[filename].Pos(int(offset)) return p.tokenFiles[filename].Pos(int(offset))
} }
// makeASTType return the ast.Expr for the given libclang type. In other words, // makeASTType return the ast.Expr for the given libclang type. In other words,
// it converts a libclang type to a type in the Go AST. // it converts a libclang type to a type in the Go AST.
func (info *fileInfo) makeASTType(typ C.CXType, pos token.Pos) ast.Expr { func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
var typeName string var typeName string
switch typ.kind { switch typ.kind {
case C.CXType_Char_S, C.CXType_Char_U: case C.CXType_Char_S, C.CXType_Char_U:
@@ -329,7 +410,7 @@ func (info *fileInfo) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
} }
return &ast.StarExpr{ return &ast.StarExpr{
Star: pos, Star: pos,
X: info.makeASTType(pointeeType, pos), X: p.makeASTType(pointeeType, pos),
} }
case C.CXType_ConstantArray: case C.CXType_ConstantArray:
return &ast.ArrayType{ return &ast.ArrayType{
@@ -339,7 +420,7 @@ func (info *fileInfo) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
Kind: token.INT, Kind: token.INT,
Value: strconv.FormatInt(int64(C.clang_getArraySize(typ)), 10), Value: strconv.FormatInt(int64(C.clang_getArraySize(typ)), 10),
}, },
Elt: info.makeASTType(C.clang_getElementType(typ), pos), Elt: p.makeASTType(C.clang_getElementType(typ), pos),
} }
case C.CXType_FunctionProto: case C.CXType_FunctionProto:
// Be compatible with gc, which uses the *[0]byte type for function // Be compatible with gc, which uses the *[0]byte type for function
@@ -360,11 +441,11 @@ func (info *fileInfo) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
} }
case C.CXType_Typedef: case C.CXType_Typedef:
name := getString(C.clang_getTypedefName(typ)) name := getString(C.clang_getTypedefName(typ))
if _, ok := info.typedefs[name]; !ok { if _, ok := p.typedefs[name]; !ok {
info.typedefs[name] = nil // don't recurse p.typedefs[name] = nil // don't recurse
c := C.tinygo_clang_getTypeDeclaration(typ) c := C.tinygo_clang_getTypeDeclaration(typ)
underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(c) underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(c)
expr := info.makeASTType(underlyingType, pos) expr := p.makeASTType(underlyingType, pos)
if strings.HasPrefix(name, "_Cgo_") { if strings.HasPrefix(name, "_Cgo_") {
expr := expr.(*ast.Ident) expr := expr.(*ast.Ident)
typeSize := C.clang_Type_getSizeOf(underlyingType) typeSize := C.clang_Type_getSizeOf(underlyingType)
@@ -406,8 +487,9 @@ func (info *fileInfo) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
} }
} }
} }
info.typedefs[name] = &typedefInfo{ p.typedefs[name] = &typedefInfo{
typeExpr: expr, typeExpr: expr,
pos: pos,
} }
} }
return &ast.Ident{ return &ast.Ident{
@@ -418,7 +500,7 @@ func (info *fileInfo) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
underlying := C.clang_Type_getNamedType(typ) underlying := C.clang_Type_getNamedType(typ)
switch underlying.kind { switch underlying.kind {
case C.CXType_Record: case C.CXType_Record:
return info.makeASTType(underlying, pos) return p.makeASTType(underlying, pos)
default: default:
panic("unknown elaborated type") panic("unknown elaborated type")
} }
@@ -434,23 +516,26 @@ func (info *fileInfo) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
default: default:
panic("unknown record declaration") panic("unknown record declaration")
} }
if _, ok := info.elaboratedTypes[cgoName]; !ok { if _, ok := p.elaboratedTypes[cgoName]; !ok {
info.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion) p.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion)
fieldList := &ast.FieldList{ fieldList := &ast.FieldList{
Opening: pos, Opening: pos,
Closing: pos, Closing: pos,
} }
ref := refMap.Put(struct { ref := storedRefs.Put(struct {
fieldList *ast.FieldList fieldList *ast.FieldList
info *fileInfo pkg *cgoPackage
}{fieldList, info}) }{fieldList, p})
defer refMap.Remove(ref) defer storedRefs.Remove(ref)
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref)) C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref))
switch C.tinygo_clang_getCursorKind(cursor) { switch C.tinygo_clang_getCursorKind(cursor) {
case C.CXCursor_StructDecl: case C.CXCursor_StructDecl:
info.elaboratedTypes[cgoName] = &ast.StructType{ p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
typeExpr: &ast.StructType{
Struct: pos, Struct: pos,
Fields: fieldList, Fields: fieldList,
},
pos: pos,
} }
case C.CXCursor_UnionDecl: case C.CXCursor_UnionDecl:
if len(fieldList.List) > 1 { if len(fieldList.List) > 1 {
@@ -480,9 +565,12 @@ func (info *fileInfo) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
} }
fieldList.List = append([]*ast.Field{unionMarker}, fieldList.List...) fieldList.List = append([]*ast.Field{unionMarker}, fieldList.List...)
} }
info.elaboratedTypes[cgoName] = &ast.StructType{ p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
typeExpr: &ast.StructType{
Struct: pos, Struct: pos,
Fields: fieldList, Fields: fieldList,
},
pos: pos,
} }
default: default:
panic("unreachable") panic("unreachable")
@@ -506,23 +594,23 @@ func (info *fileInfo) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
//export tinygo_clang_struct_visitor //export tinygo_clang_struct_visitor
func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int { func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
passed := refMap.Get(unsafe.Pointer(client_data)).(struct { passed := storedRefs.Get(unsafe.Pointer(client_data)).(struct {
fieldList *ast.FieldList fieldList *ast.FieldList
info *fileInfo pkg *cgoPackage
}) })
fieldList := passed.fieldList fieldList := passed.fieldList
info := passed.info p := passed.pkg
if C.tinygo_clang_getCursorKind(c) != C.CXCursor_FieldDecl { if C.tinygo_clang_getCursorKind(c) != C.CXCursor_FieldDecl {
panic("expected field inside cursor") panic("expected field inside cursor")
} }
name := getString(C.tinygo_clang_getCursorSpelling(c)) name := getString(C.tinygo_clang_getCursorSpelling(c))
typ := C.tinygo_clang_getCursorType(c) typ := C.tinygo_clang_getCursorType(c)
field := &ast.Field{ field := &ast.Field{
Type: info.makeASTType(typ, info.getCursorPosition(c)), Type: p.makeASTType(typ, p.getCursorPosition(c)),
} }
field.Names = []*ast.Ident{ field.Names = []*ast.Ident{
&ast.Ident{ &ast.Ident{
NamePos: info.getCursorPosition(c), NamePos: p.getCursorPosition(c),
Name: name, Name: name,
Obj: &ast.Object{ Obj: &ast.Object{
Kind: ast.Var, Kind: ast.Var,
@@ -1,6 +1,6 @@
// +build !byollvm // +build !byollvm
package loader package cgo
/* /*
#cgo linux CFLAGS: -I/usr/lib/llvm-8/include #cgo linux CFLAGS: -I/usr/lib/llvm-8/include
@@ -49,6 +49,10 @@ CXSourceLocation tinygo_clang_getCursorLocation(CXCursor c) {
return clang_getCursorLocation(c); return clang_getCursorLocation(c);
} }
CXSourceRange tinygo_clang_getCursorExtent(CXCursor c) {
return clang_getCursorExtent(c);
}
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(CXCursor c) { CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(CXCursor c) {
return clang_Cursor_getTranslationUnit(c); return clang_Cursor_getTranslationUnit(c);
} }
+6 -6
View File
@@ -1,4 +1,4 @@
package loader package cgo
import ( import (
"sync" "sync"
@@ -8,17 +8,17 @@ import (
// #include <stdlib.h> // #include <stdlib.h>
import "C" import "C"
// RefMap is a convenient way to store opaque references that can be passed to // refMap is a convenient way to store opaque references that can be passed to
// C. It is useful if an API uses function pointers and you cannot pass a Go // C. It is useful if an API uses function pointers and you cannot pass a Go
// pointer but only a C pointer. // pointer but only a C pointer.
type RefMap struct { type refMap struct {
refs map[unsafe.Pointer]interface{} refs map[unsafe.Pointer]interface{}
lock sync.Mutex lock sync.Mutex
} }
// Put stores a value in the map. It can later be retrieved using Get. It must // Put stores a value in the map. It can later be retrieved using Get. It must
// be removed using Remove to avoid memory leaks. // be removed using Remove to avoid memory leaks.
func (m *RefMap) Put(v interface{}) unsafe.Pointer { func (m *refMap) Put(v interface{}) unsafe.Pointer {
m.lock.Lock() m.lock.Lock()
defer m.lock.Unlock() defer m.lock.Unlock()
if m.refs == nil { if m.refs == nil {
@@ -31,14 +31,14 @@ func (m *RefMap) Put(v interface{}) unsafe.Pointer {
// Get returns a stored value previously inserted with Put. Use the same // Get returns a stored value previously inserted with Put. Use the same
// reference as you got from Put. // reference as you got from Put.
func (m *RefMap) Get(ref unsafe.Pointer) interface{} { func (m *refMap) Get(ref unsafe.Pointer) interface{} {
m.lock.Lock() m.lock.Lock()
defer m.lock.Unlock() defer m.lock.Unlock()
return m.refs[ref] return m.refs[ref]
} }
// Remove deletes a single reference from the map. // Remove deletes a single reference from the map.
func (m *RefMap) Remove(ref unsafe.Pointer) { func (m *refMap) Remove(ref unsafe.Pointer) {
m.lock.Lock() m.lock.Lock()
defer m.lock.Unlock() defer m.lock.Unlock()
delete(m.refs, ref) delete(m.refs, ref)
+40
View File
@@ -55,6 +55,25 @@ func (c *Compiler) expandFormalParamType(t llvm.Type) []llvm.Type {
} }
} }
// Expand an argument type to a list of offsets from the start of the object.
// Used together with expandFormalParam to get the offset of each value from the
// start of the non-expanded value.
func (c *Compiler) expandFormalParamOffsets(t llvm.Type) []uint64 {
switch t.TypeKind() {
case llvm.StructTypeKind:
fields := c.flattenAggregateTypeOffsets(t)
if len(fields) <= MaxFieldsPerParam {
return fields
} else {
// failed to lower
return []uint64{0}
}
default:
// TODO: split small arrays
return []uint64{0}
}
}
// Equivalent of expandFormalParamType for parameter values. // Equivalent of expandFormalParamType for parameter values.
func (c *Compiler) expandFormalParam(v llvm.Value) []llvm.Value { func (c *Compiler) expandFormalParam(v llvm.Value) []llvm.Value {
switch v.Type().TypeKind() { switch v.Type().TypeKind() {
@@ -92,6 +111,27 @@ func (c *Compiler) flattenAggregateType(t llvm.Type) []llvm.Type {
} }
} }
// Return the offsets from the start of the object if this object type were
// flattened like in flattenAggregate. Used together with flattenAggregate to
// know the start indices of each value in the non-flattened object.
func (c *Compiler) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
switch t.TypeKind() {
case llvm.StructTypeKind:
fields := make([]uint64, 0, t.StructElementTypesCount())
for fieldIndex, field := range t.StructElementTypes() {
suboffsets := c.flattenAggregateTypeOffsets(field)
offset := c.targetData.ElementOffset(t, fieldIndex)
for i := range suboffsets {
suboffsets[i] += offset
}
fields = append(fields, suboffsets...)
}
return fields
default:
return []uint64{0}
}
}
// Break down a struct into its elementary types for argument passing. The value // Break down a struct into its elementary types for argument passing. The value
// equivalent of flattenAggregateType // equivalent of flattenAggregateType
func (c *Compiler) flattenAggregate(v llvm.Value) []llvm.Value { func (c *Compiler) flattenAggregate(v llvm.Value) []llvm.Value {
+207 -31
View File
@@ -52,7 +52,6 @@ type Compiler struct {
dibuilder *llvm.DIBuilder dibuilder *llvm.DIBuilder
cu llvm.Metadata cu llvm.Metadata
difiles map[string]llvm.Metadata difiles map[string]llvm.Metadata
ditypes map[string]llvm.Metadata
machine llvm.TargetMachine machine llvm.TargetMachine
targetData llvm.TargetData targetData llvm.TargetData
intType llvm.Type intType llvm.Type
@@ -96,7 +95,6 @@ func NewCompiler(pkgName string, config Config) (*Compiler, error) {
c := &Compiler{ c := &Compiler{
Config: config, Config: config,
difiles: make(map[string]llvm.Metadata), difiles: make(map[string]llvm.Metadata),
ditypes: make(map[string]llvm.Metadata),
} }
target, err := llvm.GetTargetFromTriple(config.Triple) target, err := llvm.GetTargetFromTriple(config.Triple)
@@ -153,11 +151,15 @@ func (c *Compiler) TargetData() llvm.TargetData {
func (c *Compiler) selectGC() string { func (c *Compiler) selectGC() string {
gc := c.GC gc := c.GC
if gc == "" { if gc == "" {
gc = "dumb" gc = "leaking"
} }
return gc return gc
} }
func (c *Compiler) gcIsPrecise() bool {
return c.GC == "precise"
}
// Compile the given package path or .go file path. Return an error when this // Compile the given package path or .go file path. Return an error when this
// fails (in any stage). // fails (in any stage).
func (c *Compiler) Compile(mainPath string) []error { func (c *Compiler) Compile(mainPath string) []error {
@@ -197,7 +199,7 @@ func (c *Compiler) Compile(mainPath string) []error {
}, },
ShouldOverlay: func(path string) bool { ShouldOverlay: func(path string) bool {
switch path { switch path {
case "machine", "os", "reflect", "runtime", "sync": case "machine", "os", "reflect", "runtime", "runtime/volatile", "sync":
return true return true
default: default:
if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") { if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") {
@@ -252,7 +254,7 @@ func (c *Compiler) Compile(mainPath string) []error {
// Initialize debug information. // Initialize debug information.
if c.Debug { if c.Debug {
c.cu = c.dibuilder.CreateCompileUnit(llvm.DICompileUnit{ c.cu = c.dibuilder.CreateCompileUnit(llvm.DICompileUnit{
Language: llvm.DW_LANG_Go, Language: 0xb, // DW_LANG_C99 (0xc, off-by-one?)
File: mainPath, File: mainPath,
Dir: "", Dir: "",
Producer: "TinyGo", Producer: "TinyGo",
@@ -387,6 +389,13 @@ func (c *Compiler) Compile(mainPath string) []error {
llvm.ConstInt(c.ctx.Int32Type(), 3, false).ConstantAsMetadata(), // DWARF 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(),
llvm.GlobalContext().MDString("Dwarf Version"),
llvm.ConstInt(c.ctx.Int32Type(), 4, false).ConstantAsMetadata(),
}),
)
c.dibuilder.Finalize() c.dibuilder.Finalize()
} }
@@ -550,15 +559,23 @@ func isPointer(typ types.Type) bool {
// Get the DWARF type for this Go type. // Get the DWARF type for this Go type.
func (c *Compiler) getDIType(typ types.Type) llvm.Metadata { func (c *Compiler) getDIType(typ types.Type) llvm.Metadata {
name := typ.String()
if dityp, ok := c.ditypes[name]; ok {
return dityp
} else {
llvmType := c.getLLVMType(typ) llvmType := c.getLLVMType(typ)
sizeInBytes := c.targetData.TypeAllocSize(llvmType) sizeInBytes := c.targetData.TypeAllocSize(llvmType)
var encoding llvm.DwarfTypeEncoding
switch typ := typ.(type) { switch typ := typ.(type) {
case *types.Array:
return c.dibuilder.CreateArrayType(llvm.DIArrayType{
SizeInBits: sizeInBytes * 8,
AlignInBits: uint32(c.targetData.ABITypeAlignment(llvmType)) * 8,
ElementType: c.getDIType(typ.Elem()),
Subscripts: []llvm.DISubrange{
llvm.DISubrange{
Lo: 0,
Count: typ.Len(),
},
},
})
case *types.Basic: case *types.Basic:
var encoding llvm.DwarfTypeEncoding
if typ.Info()&types.IsBoolean != 0 { if typ.Info()&types.IsBoolean != 0 {
encoding = llvm.DW_ATE_boolean encoding = llvm.DW_ATE_boolean
} else if typ.Info()&types.IsFloat != 0 { } else if typ.Info()&types.IsFloat != 0 {
@@ -570,19 +587,138 @@ func (c *Compiler) getDIType(typ types.Type) llvm.Metadata {
} else if typ.Info()&types.IsInteger != 0 { } else if typ.Info()&types.IsInteger != 0 {
encoding = llvm.DW_ATE_signed encoding = llvm.DW_ATE_signed
} else if typ.Kind() == types.UnsafePointer { } else if typ.Kind() == types.UnsafePointer {
encoding = llvm.DW_ATE_address return c.dibuilder.CreatePointerType(llvm.DIPointerType{
Name: "unsafe.Pointer",
SizeInBits: c.targetData.TypeAllocSize(llvmType) * 8,
AlignInBits: uint32(c.targetData.ABITypeAlignment(llvmType)) * 8,
AddressSpace: 0,
})
} else if typ.Info()&types.IsString != 0 {
return c.dibuilder.CreateStructType(llvm.Metadata{}, llvm.DIStructType{
Name: "string",
SizeInBits: sizeInBytes * 8,
AlignInBits: uint32(c.targetData.ABITypeAlignment(llvmType)) * 8,
Elements: []llvm.Metadata{
c.dibuilder.CreateMemberType(llvm.Metadata{}, llvm.DIMemberType{
Name: "ptr",
SizeInBits: c.targetData.TypeAllocSize(c.i8ptrType) * 8,
AlignInBits: uint32(c.targetData.ABITypeAlignment(c.i8ptrType)) * 8,
OffsetInBits: 0,
Type: c.getDIType(types.NewPointer(types.Typ[types.Byte])),
}),
c.dibuilder.CreateMemberType(llvm.Metadata{}, llvm.DIMemberType{
Name: "len",
SizeInBits: c.targetData.TypeAllocSize(c.uintptrType) * 8,
AlignInBits: uint32(c.targetData.ABITypeAlignment(c.uintptrType)) * 8,
OffsetInBits: c.targetData.ElementOffset(llvmType, 1) * 8,
Type: c.getDIType(types.Typ[types.Uintptr]),
}),
},
})
} else {
panic("unknown basic type")
} }
case *types.Pointer: return c.dibuilder.CreateBasicType(llvm.DIBasicType{
encoding = llvm.DW_ATE_address Name: typ.String(),
}
// TODO: other types
dityp = c.dibuilder.CreateBasicType(llvm.DIBasicType{
Name: name,
SizeInBits: sizeInBytes * 8, SizeInBits: sizeInBytes * 8,
Encoding: encoding, Encoding: encoding,
}) })
c.ditypes[name] = dityp case *types.Chan:
return dityp return c.getDIType(types.NewPointer(c.ir.Program.ImportedPackage("runtime").Members["channel"].(*ssa.Type).Type()))
case *types.Interface:
return c.getDIType(c.ir.Program.ImportedPackage("runtime").Members["_interface"].(*ssa.Type).Type())
case *types.Map:
return c.getDIType(types.NewPointer(c.ir.Program.ImportedPackage("runtime").Members["hashmap"].(*ssa.Type).Type()))
case *types.Named:
return c.dibuilder.CreateTypedef(llvm.DITypedef{
Type: c.getDIType(typ.Underlying()),
Name: typ.String(),
})
case *types.Pointer:
return c.dibuilder.CreatePointerType(llvm.DIPointerType{
Pointee: c.getDIType(typ.Elem()),
SizeInBits: c.targetData.TypeAllocSize(llvmType) * 8,
AlignInBits: uint32(c.targetData.ABITypeAlignment(llvmType)) * 8,
AddressSpace: 0,
})
case *types.Signature:
// actually a closure
fields := llvmType.StructElementTypes()
return c.dibuilder.CreateStructType(llvm.Metadata{}, llvm.DIStructType{
SizeInBits: sizeInBytes * 8,
AlignInBits: uint32(c.targetData.ABITypeAlignment(llvmType)) * 8,
Elements: []llvm.Metadata{
c.dibuilder.CreateMemberType(llvm.Metadata{}, llvm.DIMemberType{
Name: "context",
SizeInBits: c.targetData.TypeAllocSize(fields[1]) * 8,
AlignInBits: uint32(c.targetData.ABITypeAlignment(fields[1])) * 8,
OffsetInBits: 0,
Type: c.getDIType(types.Typ[types.UnsafePointer]),
}),
c.dibuilder.CreateMemberType(llvm.Metadata{}, llvm.DIMemberType{
Name: "fn",
SizeInBits: c.targetData.TypeAllocSize(fields[0]) * 8,
AlignInBits: uint32(c.targetData.ABITypeAlignment(fields[0])) * 8,
OffsetInBits: c.targetData.ElementOffset(llvmType, 1) * 8,
Type: c.getDIType(types.Typ[types.UnsafePointer]),
}),
},
})
case *types.Slice:
fields := llvmType.StructElementTypes()
return c.dibuilder.CreateStructType(llvm.Metadata{}, llvm.DIStructType{
Name: typ.String(),
SizeInBits: sizeInBytes * 8,
AlignInBits: uint32(c.targetData.ABITypeAlignment(llvmType)) * 8,
Elements: []llvm.Metadata{
c.dibuilder.CreateMemberType(llvm.Metadata{}, llvm.DIMemberType{
Name: "ptr",
SizeInBits: c.targetData.TypeAllocSize(fields[0]) * 8,
AlignInBits: uint32(c.targetData.ABITypeAlignment(fields[0])) * 8,
OffsetInBits: 0,
Type: c.getDIType(types.NewPointer(typ.Elem())),
}),
c.dibuilder.CreateMemberType(llvm.Metadata{}, llvm.DIMemberType{
Name: "len",
SizeInBits: c.targetData.TypeAllocSize(c.uintptrType) * 8,
AlignInBits: uint32(c.targetData.ABITypeAlignment(c.uintptrType)) * 8,
OffsetInBits: c.targetData.ElementOffset(llvmType, 1) * 8,
Type: c.getDIType(types.Typ[types.Uintptr]),
}),
c.dibuilder.CreateMemberType(llvm.Metadata{}, llvm.DIMemberType{
Name: "cap",
SizeInBits: c.targetData.TypeAllocSize(c.uintptrType) * 8,
AlignInBits: uint32(c.targetData.ABITypeAlignment(c.uintptrType)) * 8,
OffsetInBits: c.targetData.ElementOffset(llvmType, 2) * 8,
Type: c.getDIType(types.Typ[types.Uintptr]),
}),
},
})
case *types.Struct:
elements := make([]llvm.Metadata, typ.NumFields())
for i := range elements {
field := typ.Field(i)
fieldType := field.Type()
if _, ok := fieldType.Underlying().(*types.Pointer); ok {
// XXX hack to avoid recursive types
fieldType = types.Typ[types.UnsafePointer]
}
llvmField := c.getLLVMType(fieldType)
elements[i] = c.dibuilder.CreateMemberType(llvm.Metadata{}, llvm.DIMemberType{
Name: field.Name(),
SizeInBits: c.targetData.TypeAllocSize(llvmField) * 8,
AlignInBits: uint32(c.targetData.ABITypeAlignment(llvmField)) * 8,
OffsetInBits: c.targetData.ElementOffset(llvmType, i) * 8,
Type: c.getDIType(fieldType),
})
}
return c.dibuilder.CreateStructType(llvm.Metadata{}, llvm.DIStructType{
SizeInBits: sizeInBytes * 8,
AlignInBits: uint32(c.targetData.ABITypeAlignment(llvmType)) * 8,
Elements: elements,
})
default:
panic("unknown type while generating DWARF debug type: " + typ.String())
} }
} }
@@ -696,6 +832,14 @@ func (c *Compiler) parseFunc(frame *Frame) {
frame.fn.LLVMFn.SetFunctionCallConv(85) // CallingConv::AVR_SIGNAL frame.fn.LLVMFn.SetFunctionCallConv(85) // CallingConv::AVR_SIGNAL
} }
// Some functions have a pragma controlling the inlining level.
switch frame.fn.Inline() {
case ir.InlineHint:
// Add LLVM inline hint to functions with //go:inline pragma.
inline := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("inlinehint"), 0)
frame.fn.LLVMFn.AddFunctionAttr(inline)
}
// Add debug info, if needed. // Add debug info, if needed.
if c.Debug { if c.Debug {
if frame.fn.Synthetic == "package initializer" { if frame.fn.Synthetic == "package initializer" {
@@ -733,15 +877,30 @@ func (c *Compiler) parseFunc(frame *Frame) {
// Add debug information to this parameter (if available) // Add debug information to this parameter (if available)
if c.Debug && frame.fn.Syntax() != nil { if c.Debug && frame.fn.Syntax() != nil {
pos := c.ir.Program.Fset.Position(frame.fn.Syntax().Pos()) pos := c.ir.Program.Fset.Position(frame.fn.Syntax().Pos())
c.dibuilder.CreateParameterVariable(frame.difunc, llvm.DIParameterVariable{ diType := c.getDIType(param.Type())
dbgParam := c.dibuilder.CreateParameterVariable(frame.difunc, llvm.DIParameterVariable{
Name: param.Name(), Name: param.Name(),
File: c.difiles[pos.Filename], File: c.difiles[pos.Filename],
Line: pos.Line, Line: pos.Line,
Type: c.getDIType(param.Type()), Type: diType,
AlwaysPreserve: true, AlwaysPreserve: true,
ArgNo: i + 1, ArgNo: i + 1,
}) })
// TODO: set the value of this parameter. loc := c.builder.GetCurrentDebugLocation()
if len(fields) == 1 {
expr := c.dibuilder.CreateExpression(nil)
c.dibuilder.InsertValueAtEnd(fields[0], dbgParam, expr, loc, entryBlock)
} else {
fieldOffsets := c.expandFormalParamOffsets(llvmType)
for i, field := range fields {
expr := c.dibuilder.CreateExpression([]int64{
0x1000, // DW_OP_LLVM_fragment
int64(fieldOffsets[i]) * 8, // offset in bits
int64(c.targetData.TypeAllocSize(field.Type())) * 8, // size in bits
})
c.dibuilder.InsertValueAtEnd(field, dbgParam, expr, loc, entryBlock)
}
}
} }
} }
@@ -907,6 +1066,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) {
case *ssa.Store: case *ssa.Store:
llvmAddr := c.getValue(frame, instr.Addr) llvmAddr := c.getValue(frame, instr.Addr)
llvmVal := c.getValue(frame, instr.Val) llvmVal := c.getValue(frame, instr.Val)
c.emitNilCheck(frame, llvmAddr, "store")
if c.targetData.TypeAllocSize(llvmVal.Type()) == 0 { if c.targetData.TypeAllocSize(llvmVal.Type()) == 0 {
// nothing to store // nothing to store
return return
@@ -1117,17 +1277,22 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, e
// Try to call the function directly for trivially static calls. // Try to call the function directly for trivially static calls.
if fn := instr.StaticCallee(); fn != nil { if fn := instr.StaticCallee(); fn != nil {
switch fn.RelString(nil) { name := fn.RelString(nil)
case "device/arm.ReadRegister": switch {
case name == "device/arm.ReadRegister":
return c.emitReadRegister(instr.Args) return c.emitReadRegister(instr.Args)
case "device/arm.Asm", "device/avr.Asm": case name == "device/arm.Asm" || name == "device/avr.Asm":
return c.emitAsm(instr.Args) return c.emitAsm(instr.Args)
case "device/arm.AsmFull", "device/avr.AsmFull": case name == "device/arm.AsmFull" || name == "device/avr.AsmFull":
return c.emitAsmFull(frame, instr) return c.emitAsmFull(frame, instr)
case "device/arm.SVCall0", "device/arm.SVCall1", "device/arm.SVCall2", "device/arm.SVCall3", "device/arm.SVCall4": case strings.HasPrefix(name, "device/arm.SVCall"):
return c.emitSVCall(frame, instr.Args) return c.emitSVCall(frame, instr.Args)
case "syscall.Syscall", "syscall.Syscall6", "syscall.Syscall9": case strings.HasPrefix(name, "syscall.Syscall"):
return c.emitSyscall(frame, instr) return c.emitSyscall(frame, instr)
case strings.HasPrefix(name, "runtime/volatile.Load"):
return c.emitVolatileLoad(frame, instr)
case strings.HasPrefix(name, "runtime/volatile.Store"):
return c.emitVolatileStore(frame, instr)
} }
targetFunc := c.ir.GetFunction(fn) targetFunc := c.ir.GetFunction(fn)
@@ -1416,7 +1581,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
return c.parseMakeClosure(frame, expr) return c.parseMakeClosure(frame, expr)
case *ssa.MakeInterface: case *ssa.MakeInterface:
val := c.getValue(frame, expr.X) val := c.getValue(frame, expr.X)
return c.parseMakeInterface(val, expr.X.Type(), expr.Pos()) return c.parseMakeInterface(val, expr.X.Type(), expr.Pos()), nil
case *ssa.MakeMap: case *ssa.MakeMap:
mapType := expr.Type().Underlying().(*types.Map) mapType := expr.Type().Underlying().(*types.Map)
llvmKeyType := c.getLLVMType(mapType.Key().Underlying()) llvmKeyType := c.getLLVMType(mapType.Key().Underlying())
@@ -1425,7 +1590,16 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
valueSize := c.targetData.TypeAllocSize(llvmValueType) valueSize := c.targetData.TypeAllocSize(llvmValueType)
llvmKeySize := llvm.ConstInt(c.ctx.Int8Type(), keySize, false) llvmKeySize := llvm.ConstInt(c.ctx.Int8Type(), keySize, false)
llvmValueSize := llvm.ConstInt(c.ctx.Int8Type(), valueSize, false) llvmValueSize := llvm.ConstInt(c.ctx.Int8Type(), valueSize, false)
hashmap := c.createRuntimeCall("hashmapMake", []llvm.Value{llvmKeySize, llvmValueSize}, "") sizeHint := llvm.ConstInt(c.uintptrType, 8, false)
if expr.Reserve != nil {
sizeHint = c.getValue(frame, expr.Reserve)
var err error
sizeHint, err = c.parseConvert(expr.Reserve.Type(), types.Typ[types.Uintptr], sizeHint, expr.Pos())
if err != nil {
return llvm.Value{}, err
}
}
hashmap := c.createRuntimeCall("hashmapMake", []llvm.Value{llvmKeySize, llvmValueSize, sizeHint}, "")
return hashmap, nil return hashmap, nil
case *ssa.MakeSlice: case *ssa.MakeSlice:
sliceLen := c.getValue(frame, expr.Len) sliceLen := c.getValue(frame, expr.Len)
@@ -1944,10 +2118,12 @@ func (c *Compiler) parseBinOp(op token.Token, typ types.Type, x, y llvm.Value, p
default: default:
return llvm.Value{}, c.makeError(pos, "binop on interface: "+op.String()) return llvm.Value{}, c.makeError(pos, "binop on interface: "+op.String())
} }
case *types.Map, *types.Pointer: case *types.Chan, *types.Map, *types.Pointer:
// Maps are in general not comparable, but can be compared against nil // Maps are in general not comparable, but can be compared against nil
// (which is a nil pointer). This means they can be trivially compared // (which is a nil pointer). This means they can be trivially compared
// by treating them as a pointer. // by treating them as a pointer.
// Channels behave as pointers in that they are equal as long as they
// are created with the same call to make or if both are nil.
switch op { switch op {
case token.EQL: // == case token.EQL: // ==
return c.builder.CreateICmp(llvm.IntEQ, x, y, ""), nil return c.builder.CreateICmp(llvm.IntEQ, x, y, ""), nil
+104
View File
@@ -0,0 +1,104 @@
package compiler
import (
"math/big"
"tinygo.org/x/go-llvm"
)
func (c *Compiler) addGlobalsBitmap() {
if c.mod.NamedGlobal("runtime.trackedGlobalsStart").IsNil() {
return // nothing to do: no GC in use
}
var trackedGlobals []llvm.Value
var trackedGlobalTypes []llvm.Type
for global := c.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
if global.IsDeclaration() {
continue
}
typ := global.Type().ElementType()
ptrs := c.getPointerBitmap(typ, global.Name())
if ptrs.BitLen() == 0 {
continue
}
trackedGlobals = append(trackedGlobals, global)
trackedGlobalTypes = append(trackedGlobalTypes, typ)
}
//
globalsBundleType := c.ctx.StructType(trackedGlobalTypes, false)
globalsBundle := llvm.AddGlobal(c.mod, globalsBundleType, "tinygo.trackedGlobals")
globalsBundle.SetLinkage(llvm.InternalLinkage)
globalsBundle.SetUnnamedAddr(true)
initializer := llvm.Undef(globalsBundleType)
for i, global := range trackedGlobals {
initializer = llvm.ConstInsertValue(initializer, global.Initializer(), []uint32{uint32(i)})
gep := llvm.ConstGEP(globalsBundle, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
})
global.ReplaceAllUsesWith(gep)
global.EraseFromParentAsGlobal()
}
globalsBundle.SetInitializer(initializer)
trackedGlobalsStart := llvm.ConstPtrToInt(globalsBundle, c.uintptrType)
c.mod.NamedGlobal("runtime.trackedGlobalsStart").SetInitializer(trackedGlobalsStart)
alignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
trackedGlobalsLength := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(globalsBundleType)/uint64(alignment), false)
c.mod.NamedGlobal("runtime.trackedGlobalsLength").SetInitializer(trackedGlobalsLength)
bitmapBytes := c.getPointerBitmap(globalsBundleType, "globals bundle").Bytes()
bitmapValues := make([]llvm.Value, len(bitmapBytes))
for i, b := range bitmapBytes {
bitmapValues[len(bitmapBytes)-i-1] = llvm.ConstInt(c.ctx.Int8Type(), uint64(b), false)
}
bitmapArray := llvm.ConstArray(llvm.ArrayType(c.ctx.Int8Type(), len(bitmapBytes)), bitmapValues)
bitmapNew := llvm.AddGlobal(c.mod, bitmapArray.Type(), "runtime.trackedGlobalsBitmap.tmp")
bitmapOld := c.mod.NamedGlobal("runtime.trackedGlobalsBitmap")
bitmapOld.ReplaceAllUsesWith(bitmapNew)
bitmapNew.SetInitializer(bitmapArray)
bitmapNew.SetName("runtime.trackedGlobalsBitmap")
}
func (c *Compiler) getPointerBitmap(typ llvm.Type, name string) *big.Int {
alignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
switch typ.TypeKind() {
case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind:
return big.NewInt(0)
case llvm.PointerTypeKind:
return big.NewInt(1)
case llvm.StructTypeKind:
ptrs := big.NewInt(0)
for i, subtyp := range typ.StructElementTypes() {
subptrs := c.getPointerBitmap(subtyp, name)
if subptrs.BitLen() == 0 {
continue
}
offset := c.targetData.ElementOffset(typ, i)
if offset%uint64(alignment) != 0 {
panic("precise GC: global contains unaligned pointer: " + name)
}
subptrs.Lsh(subptrs, uint(offset)/uint(alignment))
ptrs.Or(ptrs, subptrs)
}
return ptrs
case llvm.ArrayTypeKind:
subtyp := typ.ElementType()
subptrs := c.getPointerBitmap(subtyp, name)
ptrs := big.NewInt(0)
if subptrs.BitLen() == 0 {
return ptrs
}
elementSize := c.targetData.TypeAllocSize(subtyp)
for i := 0; i < typ.ArrayLength(); i++ {
ptrs.Lsh(ptrs, uint(elementSize)/uint(alignment))
ptrs.Or(ptrs, subptrs)
}
return ptrs
default:
panic("unknown type kind of global: " + name)
}
}
+12 -18
View File
@@ -22,13 +22,10 @@ import (
// value field. // value field.
// //
// An interface value is a {typecode, value} tuple, or {i16, i8*} to be exact. // An interface value is a {typecode, value} tuple, or {i16, i8*} to be exact.
func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) (llvm.Value, error) { func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value {
itfValue := c.emitPointerPack([]llvm.Value{val}) itfValue := c.emitPointerPack([]llvm.Value{val})
itfTypeCodeGlobal := c.getTypeCode(typ) itfTypeCodeGlobal := c.getTypeCode(typ)
itfMethodSetGlobal, err := c.getTypeMethodSet(typ) itfMethodSetGlobal := c.getTypeMethodSet(typ)
if err != nil {
return llvm.Value{}, nil
}
itfConcreteTypeGlobal := c.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name()) itfConcreteTypeGlobal := c.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name())
if itfConcreteTypeGlobal.IsNil() { if itfConcreteTypeGlobal.IsNil() {
typeInInterface := c.mod.GetTypeByName("runtime.typeInInterface") typeInInterface := c.mod.GetTypeByName("runtime.typeInInterface")
@@ -41,7 +38,7 @@ func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.
itf := llvm.Undef(c.mod.GetTypeByName("runtime._interface")) itf := llvm.Undef(c.mod.GetTypeByName("runtime._interface"))
itf = c.builder.CreateInsertValue(itf, itfTypeCode, 0, "") itf = c.builder.CreateInsertValue(itf, itfTypeCode, 0, "")
itf = c.builder.CreateInsertValue(itf, itfValue, 1, "") itf = c.builder.CreateInsertValue(itf, itfValue, 1, "")
return itf, nil return itf
} }
// getTypeCode returns a reference to a type code. // getTypeCode returns a reference to a type code.
@@ -155,18 +152,18 @@ func getTypeCodeName(t types.Type) string {
// getTypeMethodSet returns a reference (GEP) to a global method set. This // getTypeMethodSet returns a reference (GEP) to a global method set. This
// method set should be unreferenced after the interface lowering pass. // method set should be unreferenced after the interface lowering pass.
func (c *Compiler) getTypeMethodSet(typ types.Type) (llvm.Value, error) { func (c *Compiler) getTypeMethodSet(typ types.Type) llvm.Value {
global := c.mod.NamedGlobal(typ.String() + "$methodset") global := c.mod.NamedGlobal(typ.String() + "$methodset")
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false) zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
if !global.IsNil() { if !global.IsNil() {
// the method set already exists // the method set already exists
return llvm.ConstGEP(global, []llvm.Value{zero, zero}), nil return llvm.ConstGEP(global, []llvm.Value{zero, zero})
} }
ms := c.ir.Program.MethodSets.MethodSet(typ) ms := c.ir.Program.MethodSets.MethodSet(typ)
if ms.Len() == 0 { if ms.Len() == 0 {
// no methods, so can leave that one out // no methods, so can leave that one out
return llvm.ConstPointerNull(llvm.PointerType(c.mod.GetTypeByName("runtime.interfaceMethodInfo"), 0)), nil return llvm.ConstPointerNull(llvm.PointerType(c.mod.GetTypeByName("runtime.interfaceMethodInfo"), 0))
} }
methods := make([]llvm.Value, ms.Len()) methods := make([]llvm.Value, ms.Len())
@@ -179,10 +176,7 @@ func (c *Compiler) getTypeMethodSet(typ types.Type) (llvm.Value, error) {
// compiler error, so panic // compiler error, so panic
panic("cannot find function: " + f.LinkName()) panic("cannot find function: " + f.LinkName())
} }
fn, err := c.getInterfaceInvokeWrapper(f) fn := c.getInterfaceInvokeWrapper(f)
if err != nil {
return llvm.Value{}, err
}
methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{ methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{
signatureGlobal, signatureGlobal,
llvm.ConstPtrToInt(fn, c.uintptrType), llvm.ConstPtrToInt(fn, c.uintptrType),
@@ -195,7 +189,7 @@ func (c *Compiler) getTypeMethodSet(typ types.Type) (llvm.Value, error) {
global.SetInitializer(value) global.SetInitializer(value)
global.SetGlobalConstant(true) global.SetGlobalConstant(true)
global.SetLinkage(llvm.PrivateLinkage) global.SetLinkage(llvm.PrivateLinkage)
return llvm.ConstGEP(global, []llvm.Value{zero, zero}), nil return llvm.ConstGEP(global, []llvm.Value{zero, zero})
} }
// getInterfaceMethodSet returns a global variable with the method set of the // getInterfaceMethodSet returns a global variable with the method set of the
@@ -365,12 +359,12 @@ type interfaceInvokeWrapper struct {
// the underlying value, dereferences it, and calls the real method. This // the underlying value, dereferences it, and calls the real method. This
// wrapper is only needed when the interface value actually doesn't fit in a // wrapper is only needed when the interface value actually doesn't fit in a
// pointer and a pointer to the value must be created. // pointer and a pointer to the value must be created.
func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) (llvm.Value, error) { func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
wrapperName := f.LinkName() + "$invoke" wrapperName := f.LinkName() + "$invoke"
wrapper := c.mod.NamedFunction(wrapperName) wrapper := c.mod.NamedFunction(wrapperName)
if !wrapper.IsNil() { if !wrapper.IsNil() {
// Wrapper already created. Return it directly. // Wrapper already created. Return it directly.
return wrapper, nil return wrapper
} }
// Get the expanded receiver type. // Get the expanded receiver type.
@@ -383,7 +377,7 @@ func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) (llvm.Value, error)
// Casting a function signature to a different signature and calling it // Casting a function signature to a different signature and calling it
// with a receiver pointer bitcasted to *i8 (as done in calls on an // with a receiver pointer bitcasted to *i8 (as done in calls on an
// interface) is hopefully a safe (defined) operation. // interface) is hopefully a safe (defined) operation.
return f.LLVMFn, nil return f.LLVMFn
} }
// create wrapper function // create wrapper function
@@ -396,7 +390,7 @@ func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) (llvm.Value, error)
wrapper: wrapper, wrapper: wrapper,
receiverType: receiverType, receiverType: receiverType,
}) })
return wrapper, nil return wrapper
} }
// createInterfaceInvokeWrapper finishes the work of getInterfaceInvokeWrapper, // createInterfaceInvokeWrapper finishes the work of getInterfaceInvokeWrapper,
+30
View File
@@ -22,6 +22,36 @@ func getUses(value llvm.Value) []llvm.Value {
return uses return uses
} }
// createEntryBlockAlloca creates a new alloca in the entry block, even though
// the IR builder is located elsewhere. It assumes that the insert point is
// after the last instruction in the current block. Also, it adds lifetime
// information to the IR signalling that the alloca won't be used before this
// point.
//
// This is useful for creating temporary allocas for intrinsics. Don't forget to
// end the lifetime after you're done with it.
func (c *Compiler) createEntryBlockAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
currentBlock := c.builder.GetInsertBlock()
c.builder.SetInsertPointBefore(currentBlock.Parent().EntryBasicBlock().FirstInstruction())
alloca = c.builder.CreateAlloca(t, name)
c.builder.SetInsertPointAtEnd(currentBlock)
bitcast = c.builder.CreateBitCast(alloca, c.i8ptrType, name+".bitcast")
size = llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(t), false)
c.builder.CreateCall(c.getLifetimeStartFunc(), []llvm.Value{size, bitcast}, "")
return
}
// getLifetimeStartFunc returns the llvm.lifetime.start intrinsic and creates it
// first if it doesn't exist yet.
func (c *Compiler) getLifetimeStartFunc() llvm.Value {
fn := c.mod.NamedFunction("llvm.lifetime.start.p0i8")
if fn.IsNil() {
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.ctx.Int64Type(), c.i8ptrType}, false)
fn = llvm.AddFunction(c.mod, "llvm.lifetime.start.p0i8", fnType)
}
return fn
}
// getLifetimeEndFunc returns the llvm.lifetime.end intrinsic and creates it // getLifetimeEndFunc returns the llvm.lifetime.end intrinsic and creates it
// first if it doesn't exist yet. // first if it doesn't exist yet.
func (c *Compiler) getLifetimeEndFunc() llvm.Value { func (c *Compiler) getLifetimeEndFunc() llvm.Value {
+26 -12
View File
@@ -11,8 +11,13 @@ import (
func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) { func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) {
llvmValueType := c.getLLVMType(valueType) llvmValueType := c.getLLVMType(valueType)
mapValueAlloca := c.builder.CreateAlloca(llvmValueType, "hashmap.value")
mapValuePtr := c.builder.CreateBitCast(mapValueAlloca, c.i8ptrType, "hashmap.valueptr") // Allocate the memory for the resulting type. Do not zero this memory: it
// will be zeroed by the hashmap get implementation if the key is not
// present in the map.
mapValueAlloca, mapValuePtr, mapValueSize := c.createEntryBlockAlloca(llvmValueType, "hashmap.value")
// Do the lookup. How it is done depends on the key type.
var commaOkValue llvm.Value var commaOkValue llvm.Value
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string // key is a string
@@ -20,15 +25,24 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu
commaOkValue = c.createRuntimeCall("hashmapStringGet", params, "") commaOkValue = c.createRuntimeCall("hashmapStringGet", params, "")
} else if hashmapIsBinaryKey(keyType) { } else if hashmapIsBinaryKey(keyType) {
// key can be compared with runtime.memequal // key can be compared with runtime.memequal
keyAlloca := c.builder.CreateAlloca(key.Type(), "hashmap.key") // Store the key in an alloca, in the entry block to avoid dynamic stack
c.builder.CreateStore(key, keyAlloca) // growth.
keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr") mapKeyAlloca, mapKeyPtr, mapKeySize := c.createEntryBlockAlloca(key.Type(), "hashmap.key")
params := []llvm.Value{m, keyPtr, mapValuePtr} c.builder.CreateStore(key, mapKeyAlloca)
// Fetch the value from the hashmap.
params := []llvm.Value{m, mapKeyPtr, mapValuePtr}
commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "") commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "")
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{mapKeySize, mapKeyPtr}, "")
} else { } else {
// Not trivially comparable using memcmp.
return llvm.Value{}, c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String()) return llvm.Value{}, c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
} }
// Load the resulting value from the hashmap. The value is set to the zero
// value if the key doesn't exist in the hashmap.
mapValue := c.builder.CreateLoad(mapValueAlloca, "") mapValue := c.builder.CreateLoad(mapValueAlloca, "")
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{mapValueSize, mapValuePtr}, "")
if commaOk { if commaOk {
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{llvmValueType, c.ctx.Int1Type()}, false)) tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{llvmValueType, c.ctx.Int1Type()}, false))
tuple = c.builder.CreateInsertValue(tuple, mapValue, 0, "") tuple = c.builder.CreateInsertValue(tuple, mapValue, 0, "")
@@ -40,9 +54,8 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu
} }
func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) { func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) {
valueAlloca := c.builder.CreateAlloca(value.Type(), "hashmap.value") valueAlloca, valuePtr, valueSize := c.createEntryBlockAlloca(value.Type(), "hashmap.value")
c.builder.CreateStore(value, valueAlloca) c.builder.CreateStore(value, valueAlloca)
valuePtr := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "hashmap.valueptr")
keyType = keyType.Underlying() keyType = keyType.Underlying()
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string // key is a string
@@ -50,14 +63,15 @@ func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, p
c.createRuntimeCall("hashmapStringSet", params, "") c.createRuntimeCall("hashmapStringSet", params, "")
} else if hashmapIsBinaryKey(keyType) { } else if hashmapIsBinaryKey(keyType) {
// key can be compared with runtime.memequal // key can be compared with runtime.memequal
keyAlloca := c.builder.CreateAlloca(key.Type(), "hashmap.key") keyAlloca, keyPtr, keySize := c.createEntryBlockAlloca(key.Type(), "hashmap.key")
c.builder.CreateStore(key, keyAlloca) c.builder.CreateStore(key, keyAlloca)
keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr")
params := []llvm.Value{m, keyPtr, valuePtr} params := []llvm.Value{m, keyPtr, valuePtr}
c.createRuntimeCall("hashmapBinarySet", params, "") c.createRuntimeCall("hashmapBinarySet", params, "")
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{keySize, keyPtr}, "")
} else { } else {
c.addError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String()) c.addError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
} }
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{valueSize, valuePtr}, "")
} }
func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error { func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error {
@@ -68,11 +82,11 @@ func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos toke
c.createRuntimeCall("hashmapStringDelete", params, "") c.createRuntimeCall("hashmapStringDelete", params, "")
return nil return nil
} else if hashmapIsBinaryKey(keyType) { } else if hashmapIsBinaryKey(keyType) {
keyAlloca := c.builder.CreateAlloca(key.Type(), "hashmap.key") keyAlloca, keyPtr, keySize := c.createEntryBlockAlloca(key.Type(), "hashmap.key")
c.builder.CreateStore(key, keyAlloca) c.builder.CreateStore(key, keyAlloca)
keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr")
params := []llvm.Value{m, keyPtr} params := []llvm.Value{m, keyPtr}
c.createRuntimeCall("hashmapBinaryDelete", params, "") c.createRuntimeCall("hashmapBinaryDelete", params, "")
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{keySize, keyPtr}, "")
return nil return nil
} else { } else {
return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String()) return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
+8
View File
@@ -37,6 +37,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
goPasses := llvm.NewPassManager() goPasses := llvm.NewPassManager()
defer goPasses.Dispose() defer goPasses.Dispose()
goPasses.AddGlobalOptimizerPass() goPasses.AddGlobalOptimizerPass()
goPasses.AddGlobalDCEPass()
goPasses.AddConstantPropagationPass() goPasses.AddConstantPropagationPass()
goPasses.AddAggressiveDCEPass() goPasses.AddAggressiveDCEPass()
goPasses.AddFunctionAttrsPass() goPasses.AddFunctionAttrsPass()
@@ -114,6 +115,13 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
builder.Populate(modPasses) builder.Populate(modPasses)
modPasses.Run(c.mod) modPasses.Run(c.mod)
if c.gcIsPrecise() {
c.addGlobalsBitmap()
if err := c.Verify(); err != nil {
return errors.New("GC pass caused a verification failure")
}
}
return nil return nil
} }
+26
View File
@@ -0,0 +1,26 @@
package compiler
// This file implements volatile loads/stores in runtime/volatile.LoadT and
// runtime/volatile.StoreT as compiler builtins.
import (
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
func (c *Compiler) emitVolatileLoad(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
addr := c.getValue(frame, instr.Args[0])
c.emitNilCheck(frame, addr, "deref")
val := c.builder.CreateLoad(addr, "")
val.SetVolatile(true)
return val, nil
}
func (c *Compiler) emitVolatileStore(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
addr := c.getValue(frame, instr.Args[0])
val := c.getValue(frame, instr.Args[1])
c.emitNilCheck(frame, addr, "deref")
store := c.builder.CreateStore(val, addr)
store.SetVolatile(true)
return llvm.Value{}, nil
}
+3 -3
View File
@@ -25,7 +25,7 @@ func (c *Compiler) emitPointerPack(values []llvm.Value) llvm.Value {
return llvm.ConstPointerNull(c.i8ptrType) return llvm.ConstPointerNull(c.i8ptrType)
} else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind { } else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind {
return c.builder.CreateBitCast(values[0], c.i8ptrType, "pack.ptr") return c.builder.CreateBitCast(values[0], c.i8ptrType, "pack.ptr")
} else if size <= c.targetData.TypeAllocSize(c.i8ptrType) { } else if size <= c.targetData.TypeAllocSize(c.i8ptrType) && !c.gcIsPrecise() {
// Packed data fits in a pointer, so store it directly inside the // Packed data fits in a pointer, so store it directly inside the
// pointer. // pointer.
if len(values) == 1 && values[0].Type().TypeKind() == llvm.IntegerTypeKind { if len(values) == 1 && values[0].Type().TypeKind() == llvm.IntegerTypeKind {
@@ -57,7 +57,7 @@ func (c *Compiler) emitPointerPack(values []llvm.Value) llvm.Value {
return c.builder.CreateLoad(packedAlloc, "") return c.builder.CreateLoad(packedAlloc, "")
} else { } else {
// Get the original heap allocation pointer, which already is an *i8. // Get the original heap allocation pointer, which already is an *i8.
return packedHeapAlloc return c.builder.CreateBitCast(packedAlloc, c.i8ptrType, "")
} }
} }
@@ -73,7 +73,7 @@ func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []l
} else if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.PointerTypeKind { } else if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.PointerTypeKind {
// A single pointer is always stored directly. // A single pointer is always stored directly.
return []llvm.Value{c.builder.CreateBitCast(ptr, valueTypes[0], "unpack.ptr")} return []llvm.Value{c.builder.CreateBitCast(ptr, valueTypes[0], "unpack.ptr")}
} else if size <= c.targetData.TypeAllocSize(c.i8ptrType) { } else if size <= c.targetData.TypeAllocSize(c.i8ptrType) && !c.gcIsPrecise() {
// Packed data stored directly in pointer. // Packed data stored directly in pointer.
if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.IntegerTypeKind { if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.IntegerTypeKind {
// Keep this cast in SSA form. // Keep this cast in SSA form.
+2
View File
@@ -347,6 +347,8 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), implements, false)} fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), implements, false)}
case callee.Name() == "runtime.nanotime": case callee.Name() == "runtime.nanotime":
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int64Type(), 0, false)} fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int64Type(), 0, false)}
case callee.Name() == "llvm.dbg.value":
// do nothing
case strings.HasPrefix(callee.Name(), "runtime.print") || callee.Name() == "runtime._panic": case strings.HasPrefix(callee.Name(), "runtime.print") || callee.Name() == "runtime._panic":
// This are all print instructions, which necessarily have side // This are all print instructions, which necessarily have side
// effects but no results. // effects but no results.
-3
View File
@@ -18,7 +18,6 @@ type Eval struct {
TargetData llvm.TargetData TargetData llvm.TargetData
Debug bool Debug bool
builder llvm.Builder builder llvm.Builder
dibuilder *llvm.DIBuilder
dirtyGlobals map[llvm.Value]struct{} dirtyGlobals map[llvm.Value]struct{}
sideEffectFuncs map[llvm.Value]*sideEffectResult // cache of side effect scan results sideEffectFuncs map[llvm.Value]*sideEffectResult // cache of side effect scan results
} }
@@ -38,7 +37,6 @@ func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error {
dirtyGlobals: map[llvm.Value]struct{}{}, dirtyGlobals: map[llvm.Value]struct{}{},
} }
e.builder = mod.Context().NewBuilder() e.builder = mod.Context().NewBuilder()
e.dibuilder = llvm.NewDIBuilder(mod)
initAll := mod.NamedFunction(name) initAll := mod.NamedFunction(name)
bb := initAll.EntryBasicBlock() bb := initAll.EntryBasicBlock()
@@ -49,7 +47,6 @@ func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error {
e.builder.SetInsertPointBefore(bb.FirstInstruction()) e.builder.SetInsertPointBefore(bb.FirstInstruction())
dummy := e.builder.CreateAlloca(e.Mod.Context().Int8Type(), "dummy") dummy := e.builder.CreateAlloca(e.Mod.Context().Int8Type(), "dummy")
e.builder.SetInsertPointBefore(dummy) e.builder.SetInsertPointBefore(dummy)
e.builder.SetInstDebugLocation(bb.FirstInstruction())
var initCalls []llvm.Value var initCalls []llvm.Value
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) { for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
if inst == dummy { if inst == dummy {
+2
View File
@@ -35,6 +35,8 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
return &sideEffectResult{severity: sideEffectLimited} return &sideEffectResult{severity: sideEffectLimited}
case "runtime.interfaceImplements": case "runtime.interfaceImplements":
return &sideEffectResult{severity: sideEffectNone} return &sideEffectResult{severity: sideEffectNone}
case "llvm.dbg.value":
return &sideEffectResult{severity: sideEffectNone}
} }
if e.sideEffectFuncs == nil { if e.sideEffectFuncs == nil {
e.sideEffectFuncs = make(map[llvm.Value]*sideEffectResult) e.sideEffectFuncs = make(map[llvm.Value]*sideEffectResult)
+24 -1
View File
@@ -38,6 +38,7 @@ type Function struct {
nobounds bool // go:nobounds nobounds bool // go:nobounds
flag bool // used by dead code elimination flag bool // used by dead code elimination
interrupt bool // go:interrupt interrupt bool // go:interrupt
inline InlineType // go:inline
} }
// Global variable, possibly constant. // Global variable, possibly constant.
@@ -69,7 +70,22 @@ type Interface struct {
Type *types.Interface Type *types.Interface
} }
// Create and intialize a new *Program from a *ssa.Program. type InlineType int
// How much to inline.
const (
// Default behavior. The compiler decides for itself whether any given
// function will be inlined. Whether any function is inlined depends on the
// optimization level.
InlineDefault InlineType = iota
// Inline hint, just like the C inline keyword (signalled using
// //go:inline). The compiler will be more likely to inline this function,
// but it is not a guarantee.
InlineHint
)
// Create and initialize a new *Program from a *ssa.Program.
func NewProgram(lprogram *loader.Program, mainPath string) *Program { func NewProgram(lprogram *loader.Program, mainPath string) *Program {
comments := map[string]*ast.CommentGroup{} comments := map[string]*ast.CommentGroup{}
for _, pkgInfo := range lprogram.Sorted() { for _, pkgInfo := range lprogram.Sorted() {
@@ -279,6 +295,8 @@ func (f *Function) parsePragmas() {
} }
f.linkName = parts[1] f.linkName = parts[1]
f.exported = true f.exported = true
case "//go:inline":
f.inline = InlineHint
case "//go:interrupt": case "//go:interrupt":
if len(parts) != 2 { if len(parts) != 2 {
continue continue
@@ -332,6 +350,11 @@ func (f *Function) IsInterrupt() bool {
return f.interrupt return f.interrupt
} }
// Return the inline directive of this function.
func (f *Function) Inline() InlineType {
return f.inline
}
// Return the link name for this function. // Return the link name for this function.
func (f *Function) LinkName() string { func (f *Function) LinkName() string {
if f.linkName != "" { if f.linkName != "" {
+16 -16
View File
@@ -10,6 +10,8 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"sort" "sort"
"github.com/tinygo-org/tinygo/cgo"
) )
// Program holds all packages and some metadata about the program as a whole. // Program holds all packages and some metadata about the program as a whole.
@@ -33,7 +35,6 @@ type Package struct {
Imports map[string]*Package Imports map[string]*Package
Importing bool Importing bool
Files []*ast.File Files []*ast.File
tokenFiles map[string]*token.File
Pkg *types.Package Pkg *types.Package
types.Info types.Info
} }
@@ -107,7 +108,6 @@ func (p *Program) newPackage(pkg *build.Package) *Package {
Scopes: make(map[ast.Node]*types.Scope), Scopes: make(map[ast.Node]*types.Scope),
Selections: make(map[*ast.SelectorExpr]*types.Selection), Selections: make(map[*ast.SelectorExpr]*types.Selection),
}, },
tokenFiles: map[string]*token.File{},
} }
} }
@@ -295,16 +295,6 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
} }
files = append(files, f) files = append(files, f)
} }
clangIncludes := ""
if len(p.CgoFiles) != 0 {
if _, err := os.Stat(filepath.Join(p.TINYGOROOT, "llvm", "tools", "clang", "lib", "Headers")); !os.IsNotExist(err) {
// Running from the source directory.
clangIncludes = filepath.Join(p.TINYGOROOT, "llvm", "tools", "clang", "lib", "Headers")
} else {
// Running from the installation directory.
clangIncludes = filepath.Join(p.TINYGOROOT, "lib", "clang", "include")
}
}
for _, file := range p.CgoFiles { for _, file := range p.CgoFiles {
path := filepath.Join(p.Package.Dir, file) path := filepath.Join(p.Package.Dir, file)
f, err := p.parseFile(path, parser.ParseComments) f, err := p.parseFile(path, parser.ParseComments)
@@ -312,12 +302,22 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
fileErrs = append(fileErrs, err) fileErrs = append(fileErrs, err)
continue continue
} }
errs := p.processCgo(path, f, append(p.CFlags, "-I"+p.Package.Dir, "-I"+clangIncludes)) files = append(files, f)
}
if len(p.CgoFiles) != 0 {
clangIncludes := ""
if _, err := os.Stat(filepath.Join(p.TINYGOROOT, "llvm", "tools", "clang", "lib", "Headers")); !os.IsNotExist(err) {
// Running from the source directory.
clangIncludes = filepath.Join(p.TINYGOROOT, "llvm", "tools", "clang", "lib", "Headers")
} else {
// Running from the installation directory.
clangIncludes = filepath.Join(p.TINYGOROOT, "lib", "clang", "include")
}
generated, errs := cgo.Process(files, p.Program.Dir, p.fset, append(p.CFlags, "-I"+p.Package.Dir, "-I"+clangIncludes))
if errs != nil { if errs != nil {
fileErrs = append(fileErrs, errs...) fileErrs = append(fileErrs, errs...)
continue
} }
files = append(files, f) files = append(files, generated)
} }
if len(fileErrs) != 0 { if len(fileErrs) != 0 {
return nil, Errors{p, fileErrs} return nil, Errors{p, fileErrs}
@@ -346,7 +346,7 @@ func (p *Package) importRecursively() error {
p.Importing = true p.Importing = true
for _, to := range p.Package.Imports { for _, to := range p.Package.Imports {
if to == "C" { if to == "C" {
// Do Cgo processing in a later stage. // Do CGo processing in a later stage.
continue continue
} }
if _, ok := p.Imports[to]; ok { if _, ok := p.Imports[to]; ok {
+1 -1
View File
@@ -537,7 +537,7 @@ func handleCompilerError(err error) {
func main() { func main() {
outpath := flag.String("o", "", "output filename") outpath := flag.String("o", "", "output filename")
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z") opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
gc := flag.String("gc", "", "garbage collector to use (none, dumb, marksweep)") gc := flag.String("gc", "", "garbage collector to use (none, leaking, conservative)")
panicStrategy := flag.String("panic", "print", "panic strategy (abort, trap)") panicStrategy := flag.String("panic", "print", "panic strategy (abort, trap)")
printIR := flag.Bool("printir", false, "print LLVM IR") printIR := flag.Bool("printir", false, "print LLVM IR")
dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA") dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA")
+3
View File
@@ -58,6 +58,9 @@ func TestCompiler(t *testing.T) {
t.Log("running tests for emulated cortex-m3...") t.Log("running tests for emulated cortex-m3...")
for _, path := range matches { for _, path := range matches {
if path == "testdata/reflect.go" {
continue
}
t.Run(path, func(t *testing.T) { t.Run(path, func(t *testing.T) {
runTest(path, tmpdir, "qemu", t) runTest(path, tmpdir, "qemu", t)
}) })
+25
View File
@@ -0,0 +1,25 @@
// Example using the i2s hardware interface on the Adafruit Circuit Playground Express
// to read data from the onboard MEMS microphone.
//
package main
import (
"machine"
)
func main() {
machine.I2S0.Configure(machine.I2SConfig{
Mode: machine.I2SModePDM,
ClockSource: machine.I2SClockSourceExternal,
Stereo: true,
})
data := make([]uint32, 64)
for {
// get the next group of samples
machine.I2S0.Read(data)
println("data", data[0], data[1], data[2], data[4], "...")
}
}
+12
View File
@@ -99,3 +99,15 @@ const (
var ( var (
SPI0 = SPI{Bus: sam.SERCOM3_SPI} SPI0 = SPI{Bus: sam.SERCOM3_SPI}
) )
// I2S pins
const (
I2S_SCK_PIN = PA10
I2S_SD_PIN = PA08
I2S_WS_PIN = 0xff // no WS, instead uses SCK to sync
)
// I2S on the Circuit Playground Express.
var (
I2S0 = I2S{Bus: sam.I2S}
)
+12
View File
@@ -73,3 +73,15 @@ const (
var ( var (
SPI0 = SPI{Bus: sam.SERCOM4_SPI} SPI0 = SPI{Bus: sam.SERCOM4_SPI}
) )
// I2S pins
const (
I2S_SCK_PIN = PA10
I2S_SD_PIN = PA08
I2S_WS_PIN = 0xff // TODO: figure out what this is on ItsyBitsy M0.
)
// I2S on the ItsyBitsy M0.
var (
I2S0 = I2S{Bus: sam.I2S}
)
+54
View File
@@ -0,0 +1,54 @@
// +build sam
// This is the definition for I2S bus functions.
// Actual implementations if available for any given hardware
// are to be found in its the board definition.
//
// For more info about I2S, see: https://en.wikipedia.org/wiki/I%C2%B2S
//
package machine
type I2SMode uint8
type I2SStandard uint8
type I2SClockSource uint8
type I2SDataFormat uint8
const (
I2SModeMaster I2SMode = iota
I2SModeSlave
I2SModePDM
)
const (
I2StandardPhilips I2SStandard = iota
I2SStandardMSB
I2SStandardLSB
)
const (
I2SClockSourceInternal I2SClockSource = iota
I2SClockSourceExternal
)
const (
I2SDataFormatDefault I2SDataFormat = 0
I2SDataFormat8bit = 8
I2SDataFormat16bit = 16
I2SDataFormat24bit = 24
I2SDataFormat32bit = 32
)
// All fields are optional and may not be required or used on a particular platform.
type I2SConfig struct {
SCK uint8
WS uint8
SD uint8
Mode I2SMode
Standard I2SStandard
ClockSource I2SClockSource
DataFormat I2SDataFormat
AudioFrequency uint32
MasterClockOutput bool
Stereo bool
}
+50 -50
View File
@@ -10,15 +10,15 @@ import (
func (p GPIO) Configure(config GPIOConfig) { func (p GPIO) Configure(config GPIOConfig) {
if config.Mode == GPIO_OUTPUT { // set output bit if config.Mode == GPIO_OUTPUT { // set output bit
if p.Pin < 8 { if p.Pin < 8 {
*avr.DDRD |= 1 << p.Pin avr.DDRD.SetBits(1 << p.Pin)
} else { } else {
*avr.DDRB |= 1 << (p.Pin - 8) avr.DDRB.SetBits(1 << (p.Pin - 8))
} }
} else { // configure input: clear output bit } else { // configure input: clear output bit
if p.Pin < 8 { if p.Pin < 8 {
*avr.DDRD &^= 1 << p.Pin avr.DDRD.ClearBits(1 << p.Pin)
} else { } else {
*avr.DDRB &^= 1 << (p.Pin - 8) avr.DDRB.ClearBits(1 << (p.Pin - 8))
} }
} }
} }
@@ -26,15 +26,15 @@ func (p GPIO) Configure(config GPIOConfig) {
// Get returns the current value of a GPIO pin. // Get returns the current value of a GPIO pin.
func (p GPIO) Get() bool { func (p GPIO) Get() bool {
if p.Pin < 8 { if p.Pin < 8 {
val := *avr.PIND & (1 << p.Pin) val := avr.PIND.Get() & (1 << p.Pin)
return (val > 0) return (val > 0)
} else { } else {
val := *avr.PINB & (1 << (p.Pin - 8)) val := avr.PINB.Get() & (1 << (p.Pin - 8))
return (val > 0) return (val > 0)
} }
} }
func (p GPIO) getPortMask() (*avr.RegValue, uint8) { func (p GPIO) getPortMask() (*avr.Register8, uint8) {
if p.Pin < 8 { if p.Pin < 8 {
return avr.PORTD, 1 << p.Pin return avr.PORTD, 1 << p.Pin
} else { } else {
@@ -45,64 +45,64 @@ func (p GPIO) getPortMask() (*avr.RegValue, uint8) {
// InitPWM initializes the registers needed for PWM. // InitPWM initializes the registers needed for PWM.
func InitPWM() { func InitPWM() {
// use waveform generation // use waveform generation
*avr.TCCR0A |= avr.TCCR0A_WGM00 avr.TCCR0A.SetBits(avr.TCCR0A_WGM00)
// set timer 0 prescale factor to 64 // set timer 0 prescale factor to 64
*avr.TCCR0B |= avr.TCCR0B_CS01 | avr.TCCR0B_CS00 avr.TCCR0B.SetBits(avr.TCCR0B_CS01 | avr.TCCR0B_CS00)
// set timer 1 prescale factor to 64 // set timer 1 prescale factor to 64
*avr.TCCR1B |= avr.TCCR1B_CS11 avr.TCCR1B.SetBits(avr.TCCR1B_CS11)
// put timer 1 in 8-bit phase correct pwm mode // put timer 1 in 8-bit phase correct pwm mode
*avr.TCCR1A |= avr.TCCR1A_WGM10 avr.TCCR1A.SetBits(avr.TCCR1A_WGM10)
// set timer 2 prescale factor to 64 // set timer 2 prescale factor to 64
*avr.TCCR2B |= avr.TCCR2B_CS22 avr.TCCR2B.SetBits(avr.TCCR2B_CS22)
// configure timer 2 for phase correct pwm (8-bit) // configure timer 2 for phase correct pwm (8-bit)
*avr.TCCR2A |= avr.TCCR2A_WGM20 avr.TCCR2A.SetBits(avr.TCCR2A_WGM20)
} }
// Configure configures a PWM pin for output. // Configure configures a PWM pin for output.
func (pwm PWM) Configure() { func (pwm PWM) Configure() {
if pwm.Pin < 8 { if pwm.Pin < 8 {
*avr.DDRD |= 1 << pwm.Pin avr.DDRD.SetBits(1 << pwm.Pin)
} else { } else {
*avr.DDRB |= 1 << (pwm.Pin - 8) avr.DDRB.SetBits(1 << (pwm.Pin - 8))
} }
} }
// Set turns on the duty cycle for a PWM pin using the provided value. On the AVR this is normally a // Set turns on the duty cycle for a PWM pin using the provided value. On the AVR this is normally a
// 8-bit value ranging from 0 to 255. // 8-bit value ranging from 0 to 255.
func (pwm PWM) Set(value uint16) { func (pwm PWM) Set(value uint16) {
value8 := value >> 8 value8 := uint8(value >> 8)
switch pwm.Pin { switch pwm.Pin {
case 3: case 3:
// connect pwm to pin on timer 2, channel B // connect pwm to pin on timer 2, channel B
*avr.TCCR2A |= avr.TCCR2A_COM2B1 avr.TCCR2A.SetBits(avr.TCCR2A_COM2B1)
*avr.OCR2B = avr.RegValue(value8) // set pwm duty avr.OCR2B.Set(value8) // set pwm duty
case 5: case 5:
// connect pwm to pin on timer 0, channel B // connect pwm to pin on timer 0, channel B
*avr.TCCR0A |= avr.TCCR0A_COM0B1 avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1)
*avr.OCR0B = avr.RegValue(value8) // set pwm duty avr.OCR0B.Set(value8) // set pwm duty
case 6: case 6:
// connect pwm to pin on timer 0, channel A // connect pwm to pin on timer 0, channel A
*avr.TCCR0A |= avr.TCCR0A_COM0A1 avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1)
*avr.OCR0A = avr.RegValue(value8) // set pwm duty avr.OCR0A.Set(value8) // set pwm duty
case 9: case 9:
// connect pwm to pin on timer 1, channel A // connect pwm to pin on timer 1, channel A
*avr.TCCR1A |= avr.TCCR1A_COM1A1 avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1)
// this is a 16-bit value, but we only currently allow the low order bits to be set // this is a 16-bit value, but we only currently allow the low order bits to be set
*avr.OCR1AL = avr.RegValue(value8) // set pwm duty avr.OCR1AL.Set(value8) // set pwm duty
case 10: case 10:
// connect pwm to pin on timer 1, channel B // connect pwm to pin on timer 1, channel B
*avr.TCCR1A |= avr.TCCR1A_COM1B1 avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1)
// this is a 16-bit value, but we only currently allow the low order bits to be set // this is a 16-bit value, but we only currently allow the low order bits to be set
*avr.OCR1BL = avr.RegValue(value8) // set pwm duty avr.OCR1BL.Set(value8) // set pwm duty
case 11: case 11:
// connect pwm to pin on timer 2, channel A // connect pwm to pin on timer 2, channel A
*avr.TCCR2A |= avr.TCCR2A_COM2A1 avr.TCCR2A.SetBits(avr.TCCR2A_COM2A1)
*avr.OCR2A = avr.RegValue(value8) // set pwm duty avr.OCR2A.Set(value8) // set pwm duty
default: default:
panic("Invalid PWM pin") panic("Invalid PWM pin")
} }
@@ -121,19 +121,19 @@ func (i2c I2C) Configure(config I2CConfig) {
} }
// Activate internal pullups for twi. // Activate internal pullups for twi.
*avr.PORTC |= (avr.DIDR0_ADC4D | avr.DIDR0_ADC5D) avr.PORTC.SetBits((avr.DIDR0_ADC4D | avr.DIDR0_ADC5D))
// Initialize twi prescaler and bit rate. // Initialize twi prescaler and bit rate.
*avr.TWSR |= (avr.TWSR_TWPS0 | avr.TWSR_TWPS1) avr.TWSR.SetBits((avr.TWSR_TWPS0 | avr.TWSR_TWPS1))
// twi bit rate formula from atmega128 manual pg. 204: // twi bit rate formula from atmega128 manual pg. 204:
// SCL Frequency = CPU Clock Frequency / (16 + (2 * TWBR)) // SCL Frequency = CPU Clock Frequency / (16 + (2 * TWBR))
// NOTE: TWBR should be 10 or higher for master mode. // NOTE: TWBR should be 10 or higher for master mode.
// It is 72 for a 16mhz board with 100kHz TWI // It is 72 for a 16mhz board with 100kHz TWI
*avr.TWBR = avr.RegValue(((CPU_FREQUENCY / config.Frequency) - 16) / 2) avr.TWBR.Set(uint8(((CPU_FREQUENCY / config.Frequency) - 16) / 2))
// Enable twi module. // Enable twi module.
*avr.TWCR = avr.TWCR_TWEN avr.TWCR.Set(avr.TWCR_TWEN)
} }
// Tx does a single I2C transaction at the specified address. // Tx does a single I2C transaction at the specified address.
@@ -162,10 +162,10 @@ func (i2c I2C) Tx(addr uint16, w, r []byte) error {
// start starts an I2C communication session. // start starts an I2C communication session.
func (i2c I2C) start(address uint8, write bool) { func (i2c I2C) start(address uint8, write bool) {
// Clear TWI interrupt flag, put start condition on SDA, and enable TWI. // Clear TWI interrupt flag, put start condition on SDA, and enable TWI.
*avr.TWCR = (avr.TWCR_TWINT | avr.TWCR_TWSTA | avr.TWCR_TWEN) avr.TWCR.Set((avr.TWCR_TWINT | avr.TWCR_TWSTA | avr.TWCR_TWEN))
// Wait till start condition is transmitted. // Wait till start condition is transmitted.
for (*avr.TWCR & avr.TWCR_TWINT) == 0 { for (avr.TWCR.Get() & avr.TWCR_TWINT) == 0 {
} }
// Write 7-bit shifted peripheral address. // Write 7-bit shifted peripheral address.
@@ -179,36 +179,36 @@ func (i2c I2C) start(address uint8, write bool) {
// stop ends an I2C communication session. // stop ends an I2C communication session.
func (i2c I2C) stop() { func (i2c I2C) stop() {
// Send stop condition. // Send stop condition.
*avr.TWCR = (avr.TWCR_TWEN | avr.TWCR_TWINT | avr.TWCR_TWSTO) avr.TWCR.Set(avr.TWCR_TWEN | avr.TWCR_TWINT | avr.TWCR_TWSTO)
// Wait for stop condition to be executed on bus. // Wait for stop condition to be executed on bus.
for (*avr.TWCR & avr.TWCR_TWSTO) == 0 { for (avr.TWCR.Get() & avr.TWCR_TWSTO) == 0 {
} }
} }
// writeByte writes a single byte to the I2C bus. // writeByte writes a single byte to the I2C bus.
func (i2c I2C) writeByte(data byte) { func (i2c I2C) writeByte(data byte) {
// Write data to register. // Write data to register.
*avr.TWDR = avr.RegValue(data) avr.TWDR.Set(data)
// Clear TWI interrupt flag and enable TWI. // Clear TWI interrupt flag and enable TWI.
*avr.TWCR = (avr.TWCR_TWEN | avr.TWCR_TWINT) avr.TWCR.Set(avr.TWCR_TWEN | avr.TWCR_TWINT)
// Wait till data is transmitted. // Wait till data is transmitted.
for (*avr.TWCR & avr.TWCR_TWINT) == 0 { for (avr.TWCR.Get() & avr.TWCR_TWINT) == 0 {
} }
} }
// readByte reads a single byte from the I2C bus. // readByte reads a single byte from the I2C bus.
func (i2c I2C) readByte() byte { func (i2c I2C) readByte() byte {
// Clear TWI interrupt flag and enable TWI. // Clear TWI interrupt flag and enable TWI.
*avr.TWCR = (avr.TWCR_TWEN | avr.TWCR_TWINT | avr.TWCR_TWEA) avr.TWCR.Set(avr.TWCR_TWEN | avr.TWCR_TWINT | avr.TWCR_TWEA)
// Wait till read request is transmitted. // Wait till read request is transmitted.
for (*avr.TWCR & avr.TWCR_TWINT) == 0 { for (avr.TWCR.Get() & avr.TWCR_TWINT) == 0 {
} }
return byte(*avr.TWDR) return byte(avr.TWDR.Get())
} }
// UART on the AVR. // UART on the AVR.
@@ -226,32 +226,32 @@ func (uart UART) Configure(config UARTConfig) {
// https://www.microchip.com/webdoc/AVRLibcReferenceManual/FAQ_1faq_wrong_baud_rate.html // https://www.microchip.com/webdoc/AVRLibcReferenceManual/FAQ_1faq_wrong_baud_rate.html
// ((F_CPU + UART_BAUD_RATE * 8L) / (UART_BAUD_RATE * 16L) - 1) // ((F_CPU + UART_BAUD_RATE * 8L) / (UART_BAUD_RATE * 16L) - 1)
ps := ((CPU_FREQUENCY+config.BaudRate*8)/(config.BaudRate*16) - 1) ps := ((CPU_FREQUENCY+config.BaudRate*8)/(config.BaudRate*16) - 1)
*avr.UBRR0H = avr.RegValue(ps >> 8) avr.UBRR0H.Set(uint8(ps >> 8))
*avr.UBRR0L = avr.RegValue(ps & 0xff) avr.UBRR0L.Set(uint8(ps & 0xff))
// enable RX, TX and RX interrupt // enable RX, TX and RX interrupt
*avr.UCSR0B = avr.UCSR0B_RXEN0 | avr.UCSR0B_TXEN0 | avr.UCSR0B_RXCIE0 avr.UCSR0B.Set(avr.UCSR0B_RXEN0 | avr.UCSR0B_TXEN0 | avr.UCSR0B_RXCIE0)
// 8-bits data // 8-bits data
*avr.UCSR0C = avr.UCSR0C_UCSZ01 | avr.UCSR0C_UCSZ00 avr.UCSR0C.Set(avr.UCSR0C_UCSZ01 | avr.UCSR0C_UCSZ00)
} }
// WriteByte writes a byte of data to the UART. // WriteByte writes a byte of data to the UART.
func (uart UART) WriteByte(c byte) error { func (uart UART) WriteByte(c byte) error {
// Wait until UART buffer is not busy. // Wait until UART buffer is not busy.
for (*avr.UCSR0A & avr.UCSR0A_UDRE0) == 0 { for (avr.UCSR0A.Get() & avr.UCSR0A_UDRE0) == 0 {
} }
*avr.UDR0 = avr.RegValue(c) // send char avr.UDR0.Set(c) // send char
return nil return nil
} }
//go:interrupt USART_RX_vect //go:interrupt USART_RX_vect
func handleUSART_RX() { func handleUSART_RX() {
// Read register to clear it. // Read register to clear it.
data := *avr.UDR0 data := avr.UDR0.Get()
// Ensure no error. // Ensure no error.
if (*avr.UCSR0A & (avr.UCSR0A_FE0 | avr.UCSR0A_DOR0 | avr.UCSR0A_UPE0)) == 0 { if (avr.UCSR0A.Get() & (avr.UCSR0A_FE0 | avr.UCSR0A_DOR0 | avr.UCSR0A_UPE0)) == 0 {
// Put data from UDR register into buffer. // Put data from UDR register into buffer.
UART0.Receive(byte(data)) UART0.Receive(byte(data))
} }
+229
View File
@@ -808,6 +808,235 @@ func (i2c I2C) readByte() byte {
return byte(i2c.Bus.DATA) return byte(i2c.Bus.DATA)
} }
// I2S on the SAMD21.
// I2S
type I2S struct {
Bus *sam.I2S_Type
}
// Configure is used to configure the I2S interface. You must call this
// before you can use the I2S bus.
func (i2s I2S) Configure(config I2SConfig) {
// handle defaults
if config.SCK == 0 {
config.SCK = I2S_SCK_PIN
config.WS = I2S_WS_PIN
config.SD = I2S_SD_PIN
}
if config.AudioFrequency == 0 {
config.AudioFrequency = 48000
}
if config.DataFormat == I2SDataFormatDefault {
if config.Stereo {
config.DataFormat = I2SDataFormat16bit
} else {
config.DataFormat = I2SDataFormat32bit
}
}
// Turn on clock for I2S
sam.PM.APBCMASK |= sam.PM_APBCMASK_I2S_
// setting clock rate for sample.
division_factor := CPU_FREQUENCY / (config.AudioFrequency * uint32(config.DataFormat))
// Switch Generic Clock Generator 3 to DFLL48M.
sam.GCLK.GENDIV = sam.RegValue((sam.GCLK_CLKCTRL_GEN_GCLK3 << sam.GCLK_GENDIV_ID_Pos) |
(division_factor << sam.GCLK_GENDIV_DIV_Pos))
waitForSync()
sam.GCLK.GENCTRL = sam.RegValue((sam.GCLK_CLKCTRL_GEN_GCLK3 << sam.GCLK_GENCTRL_ID_Pos) |
(sam.GCLK_GENCTRL_SRC_DFLL48M << sam.GCLK_GENCTRL_SRC_Pos) |
sam.GCLK_GENCTRL_IDC |
sam.GCLK_GENCTRL_GENEN)
waitForSync()
// Use Generic Clock Generator 3 as source for I2S.
sam.GCLK.CLKCTRL = sam.RegValue16((sam.GCLK_CLKCTRL_ID_I2S_0 << sam.GCLK_CLKCTRL_ID_Pos) |
(sam.GCLK_CLKCTRL_GEN_GCLK3 << sam.GCLK_CLKCTRL_GEN_Pos) |
sam.GCLK_CLKCTRL_CLKEN)
waitForSync()
// reset the device
i2s.Bus.CTRLA |= sam.I2S_CTRLA_SWRST
for (i2s.Bus.SYNCBUSY & sam.I2S_SYNCBUSY_SWRST) > 0 {
}
// disable device before continuing
for (i2s.Bus.SYNCBUSY & sam.I2S_SYNCBUSY_ENABLE) > 0 {
}
i2s.Bus.CTRLA &^= sam.I2S_CTRLA_ENABLE
// setup clock
if config.ClockSource == I2SClockSourceInternal {
// TODO: make sure correct for I2S output
// set serial clock select pin
i2s.Bus.CLKCTRL0 |= sam.I2S_CLKCTRL_SCKSEL
// set frame select pin
i2s.Bus.CLKCTRL0 |= sam.I2S_CLKCTRL_FSSEL
} else {
// Configure FS generation from SCK clock.
i2s.Bus.CLKCTRL0 &^= sam.I2S_CLKCTRL_FSSEL
}
if config.Standard == I2StandardPhilips {
// set 1-bit delay
i2s.Bus.CLKCTRL0 |= sam.I2S_CLKCTRL_BITDELAY
} else {
// set 0-bit delay
i2s.Bus.CLKCTRL0 &^= sam.I2S_CLKCTRL_BITDELAY
}
// set number of slots.
if config.Stereo {
i2s.Bus.CLKCTRL0 |= (1 << sam.I2S_CLKCTRL_NBSLOTS_Pos)
} else {
i2s.Bus.CLKCTRL0 &^= (1 << sam.I2S_CLKCTRL_NBSLOTS_Pos)
}
// set slot size
switch config.DataFormat {
case I2SDataFormat8bit:
i2s.Bus.CLKCTRL0 |= sam.I2S_CLKCTRL_SLOTSIZE_8
case I2SDataFormat16bit:
i2s.Bus.CLKCTRL0 |= sam.I2S_CLKCTRL_SLOTSIZE_16
case I2SDataFormat24bit:
i2s.Bus.CLKCTRL0 |= sam.I2S_CLKCTRL_SLOTSIZE_24
case I2SDataFormat32bit:
i2s.Bus.CLKCTRL0 |= sam.I2S_CLKCTRL_SLOTSIZE_32
}
// configure pin for clock
GPIO{config.SCK}.Configure(GPIOConfig{Mode: GPIO_COM})
// configure pin for WS, if needed
if config.WS != 0xff {
GPIO{config.WS}.Configure(GPIOConfig{Mode: GPIO_COM})
}
// now set serializer data size.
switch config.DataFormat {
case I2SDataFormat8bit:
i2s.Bus.SERCTRL1 |= sam.I2S_SERCTRL_DATASIZE_8
case I2SDataFormat16bit:
i2s.Bus.SERCTRL1 |= sam.I2S_SERCTRL_DATASIZE_16
case I2SDataFormat24bit:
i2s.Bus.SERCTRL1 |= sam.I2S_SERCTRL_DATASIZE_24
case I2SDataFormat32bit:
case I2SDataFormatDefault:
i2s.Bus.SERCTRL1 |= sam.I2S_SERCTRL_DATASIZE_32
}
// set serializer slot adjustment
if config.Standard == I2SStandardLSB {
// adjust right
i2s.Bus.SERCTRL1 &^= sam.I2S_SERCTRL_SLOTADJ
} else {
// adjust left
i2s.Bus.SERCTRL1 |= sam.I2S_SERCTRL_SLOTADJ
// reverse bit order?
i2s.Bus.SERCTRL1 |= sam.I2S_SERCTRL_BITREV
}
// set serializer mode.
if config.Mode == I2SModePDM {
i2s.Bus.SERCTRL1 |= sam.I2S_SERCTRL_SERMODE_PDM2
} else {
i2s.Bus.SERCTRL1 |= sam.I2S_SERCTRL_SERMODE_RX
}
// configure data pin
GPIO{config.SD}.Configure(GPIOConfig{Mode: GPIO_COM})
// re-enable
i2s.Bus.CTRLA |= sam.I2S_CTRLA_ENABLE
for (i2s.Bus.SYNCBUSY & sam.I2S_SYNCBUSY_ENABLE) > 0 {
}
// enable i2s clock
i2s.Bus.CTRLA |= sam.I2S_CTRLA_CKEN0
for (i2s.Bus.SYNCBUSY & sam.I2S_SYNCBUSY_CKEN0) > 0 {
}
// enable i2s serializer
i2s.Bus.CTRLA |= sam.I2S_CTRLA_SEREN1
for (i2s.Bus.SYNCBUSY & sam.I2S_SYNCBUSY_SEREN1) > 0 {
}
}
// Read data from the I2S bus into the provided slice.
// The I2S bus must already have been configured correctly.
func (i2s I2S) Read(p []uint32) (n int, err error) {
i := 0
for i = 0; i < len(p); i++ {
// Wait until ready
for (i2s.Bus.INTFLAG & sam.I2S_INTFLAG_RXRDY1) == 0 {
}
for (i2s.Bus.SYNCBUSY & sam.I2S_SYNCBUSY_DATA1) > 0 {
}
// read data
p[i] = uint32(i2s.Bus.DATA1)
// indicate read complete
i2s.Bus.INTFLAG = sam.I2S_INTFLAG_RXRDY1
}
return i, nil
}
// Write data to the I2S bus from the provided slice.
// The I2S bus must already have been configured correctly.
func (i2s I2S) Write(p []uint32) (n int, err error) {
i := 0
for i = 0; i < len(p); i++ {
// Wait until ready
for (i2s.Bus.INTFLAG & sam.I2S_INTFLAG_TXRDY1) == 0 {
}
for (i2s.Bus.SYNCBUSY & sam.I2S_SYNCBUSY_DATA1) > 0 {
}
// write data
i2s.Bus.DATA1 = sam.RegValue(p[i])
// indicate write complete
i2s.Bus.INTFLAG = sam.I2S_INTFLAG_TXRDY1
}
return i, nil
}
// Close the I2S bus.
func (i2s I2S) Close() error {
// Sync wait
for (i2s.Bus.SYNCBUSY & sam.I2S_SYNCBUSY_ENABLE) > 0 {
}
// disable I2S
i2s.Bus.CTRLA &^= sam.I2S_CTRLA_ENABLE
return nil
}
func waitForSync() {
for (sam.GCLK.STATUS & sam.GCLK_STATUS_SYNCBUSY) > 0 {
}
}
// SPI // SPI
type SPI struct { type SPI struct {
Bus *sam.SERCOM_SPI_Type Bus *sam.SERCOM_SPI_Type
+4 -4
View File
@@ -9,19 +9,19 @@ import (
// Configure sets the pin to input or output. // Configure sets the pin to input or output.
func (p GPIO) Configure(config GPIOConfig) { func (p GPIO) Configure(config GPIOConfig) {
if config.Mode == GPIO_OUTPUT { // set output bit if config.Mode == GPIO_OUTPUT { // set output bit
*avr.DDRB |= 1 << p.Pin avr.DDRB.SetBits(1 << p.Pin)
} else { // configure input: clear output bit } else { // configure input: clear output bit
*avr.DDRB &^= 1 << p.Pin avr.DDRB.ClearBits(1 << p.Pin)
} }
} }
func (p GPIO) getPortMask() (*avr.RegValue, uint8) { func (p GPIO) getPortMask() (*avr.Register8, uint8) {
return avr.PORTB, 1 << p.Pin return avr.PORTB, 1 << p.Pin
} }
// Get returns the current value of a GPIO pin. // Get returns the current value of a GPIO pin.
func (p GPIO) Get() bool { func (p GPIO) Get() bool {
val := *avr.PINB & (1 << p.Pin) val := avr.PINB.Get() & (1 << p.Pin)
return (val > 0) return (val > 0)
} }
+12 -14
View File
@@ -17,10 +17,10 @@ const (
func (p GPIO) Set(value bool) { func (p GPIO) Set(value bool) {
if value { // set bits if value { // set bits
port, mask := p.PortMaskSet() port, mask := p.PortMaskSet()
*port = mask port.Set(mask)
} else { // clear bits } else { // clear bits
port, mask := p.PortMaskClear() port, mask := p.PortMaskClear()
*port = mask port.Set(mask)
} }
} }
@@ -30,9 +30,9 @@ func (p GPIO) Set(value bool) {
// Warning: there are no separate pin set/clear registers on the AVR. The // Warning: there are no separate pin set/clear registers on the AVR. The
// returned mask is only valid as long as no other pin in the same port has been // returned mask is only valid as long as no other pin in the same port has been
// changed. // changed.
func (p GPIO) PortMaskSet() (*avr.RegValue, avr.RegValue) { func (p GPIO) PortMaskSet() (*avr.Register8, uint8) {
port, mask := p.getPortMask() port, mask := p.getPortMask()
return port, *port | avr.RegValue(mask) return port, port.Get() | mask
} }
// Return the register and mask to disable a given port. This can be used to // Return the register and mask to disable a given port. This can be used to
@@ -41,18 +41,18 @@ func (p GPIO) PortMaskSet() (*avr.RegValue, avr.RegValue) {
// Warning: there are no separate pin set/clear registers on the AVR. The // Warning: there are no separate pin set/clear registers on the AVR. The
// returned mask is only valid as long as no other pin in the same port has been // returned mask is only valid as long as no other pin in the same port has been
// changed. // changed.
func (p GPIO) PortMaskClear() (*avr.RegValue, avr.RegValue) { func (p GPIO) PortMaskClear() (*avr.Register8, uint8) {
port, mask := p.getPortMask() port, mask := p.getPortMask()
return port, *port &^ avr.RegValue(mask) return port, port.Get() &^ mask
} }
// InitADC initializes the registers needed for ADC. // InitADC initializes the registers needed for ADC.
func InitADC() { func InitADC() {
// set a2d prescaler so we are inside the desired 50-200 KHz range at 16MHz. // set a2d prescaler so we are inside the desired 50-200 KHz range at 16MHz.
*avr.ADCSRA |= (avr.ADCSRA_ADPS2 | avr.ADCSRA_ADPS1 | avr.ADCSRA_ADPS0) avr.ADCSRA.SetBits(avr.ADCSRA_ADPS2 | avr.ADCSRA_ADPS1 | avr.ADCSRA_ADPS0)
// enable a2d conversions // enable a2d conversions
*avr.ADCSRA |= avr.ADCSRA_ADEN avr.ADCSRA.SetBits(avr.ADCSRA_ADEN)
} }
// Configure configures a ADCPin to be able to be used to read data. // Configure configures a ADCPin to be able to be used to read data.
@@ -68,18 +68,16 @@ func (a ADC) Get() uint16 {
// set the ADLAR bit (left-adjusted result) to get a value scaled to 16 // set the ADLAR bit (left-adjusted result) to get a value scaled to 16
// bits. This has the same effect as shifting the return value left by 6 // bits. This has the same effect as shifting the return value left by 6
// bits. // bits.
*avr.ADMUX = avr.RegValue(avr.ADMUX_REFS0 | avr.ADMUX_ADLAR | (a.Pin & 0x07)) avr.ADMUX.Set(avr.ADMUX_REFS0 | avr.ADMUX_ADLAR | (a.Pin & 0x07))
// start the conversion // start the conversion
*avr.ADCSRA |= avr.ADCSRA_ADSC avr.ADCSRA.SetBits(avr.ADCSRA_ADSC)
// ADSC is cleared when the conversion finishes // ADSC is cleared when the conversion finishes
for ok := true; ok; ok = (*avr.ADCSRA & avr.ADCSRA_ADSC) > 0 { for ok := true; ok; ok = (avr.ADCSRA.Get() & avr.ADCSRA_ADSC) > 0 {
} }
low := uint16(*avr.ADCL) return uint16(avr.ADCL.Get()) | uint16(avr.ADCH.Get())<<8
high := uint16(*avr.ADCH)
return uint16(low) | uint16(high<<8)
} }
// I2C on AVR. // I2C on AVR.
+7 -18
View File
@@ -185,21 +185,20 @@ func (i2c I2C) Tx(addr uint16, w, r []byte) error {
} }
} }
if len(r) != 0 { if len(r) != 0 {
// To trigger suspend task when a byte is received
i2c.Bus.SHORTS = nrf.TWI_SHORTS_BB_SUSPEND
i2c.Bus.TASKS_STARTRX = 1 // re-start transmission for reading i2c.Bus.TASKS_STARTRX = 1 // re-start transmission for reading
for i := range r { // read each char for i := range r { // read each char
if i+1 == len(r) { if i+1 == len(r) {
// The 'stop' signal must be sent before reading back the last // To trigger stop task when last byte is received, set before resume task.
// byte, so that it will be sent by the I2C peripheral right i2c.Bus.SHORTS = nrf.TWI_SHORTS_BB_STOP
// after the last byte has been read. }
r[i] = i2c.readLastByte() i2c.Bus.TASKS_RESUME = 1 // re-start transmission for reading
} else {
r[i] = i2c.readByte() r[i] = i2c.readByte()
} }
} }
} else {
// Nothing to read back. Stop the transmission.
i2c.signalStop() i2c.signalStop()
} i2c.Bus.SHORTS = nrf.TWI_SHORTS_BB_SUSPEND_Disabled
return nil return nil
} }
@@ -229,16 +228,6 @@ func (i2c I2C) readByte() byte {
return byte(i2c.Bus.RXD) return byte(i2c.Bus.RXD)
} }
// readLastByte reads a single byte from the I2C bus, sending a stop signal
// after it has been read.
func (i2c I2C) readLastByte() byte {
for i2c.Bus.EVENTS_RXDREADY == 0 {
}
i2c.Bus.EVENTS_RXDREADY = 0
i2c.signalStop() // signal 'stop' now, so it is sent when reading RXD
return byte(i2c.Bus.RXD)
}
// SPI on the NRF. // SPI on the NRF.
type SPI struct { type SPI struct {
Bus *nrf.SPI_Type Bus *nrf.SPI_Type
@@ -1,5 +1,3 @@
// +build gc.marksweep
package runtime package runtime
// This memory manager is a textbook mark/sweep implementation, heavily inspired // This memory manager is a textbook mark/sweep implementation, heavily inspired
@@ -168,42 +166,9 @@ func (b gcBlock) unmark() {
} }
} }
// Initialize the memory allocator.
// No memory may be allocated before this is called. That means the runtime and
// any packages the runtime depends upon may not allocate memory during package
// initialization.
func init() {
totalSize := heapEnd - heapStart
// Allocate some memory to keep 2 bits of information about every block.
metadataSize := totalSize / (blocksPerStateByte * bytesPerBlock)
// Align the pool.
poolStart = (heapStart + metadataSize + (bytesPerBlock - 1)) &^ (bytesPerBlock - 1)
poolEnd := heapEnd &^ (bytesPerBlock - 1)
numBlocks := (poolEnd - poolStart) / bytesPerBlock
endBlock = gcBlock(numBlocks)
if gcDebug {
println("heapStart: ", heapStart)
println("heapEnd: ", heapEnd)
println("total size: ", totalSize)
println("metadata size: ", metadataSize)
println("poolStart: ", poolStart)
println("# of blocks: ", numBlocks)
println("# of block states:", metadataSize*blocksPerStateByte)
}
if gcAsserts && metadataSize*blocksPerStateByte < numBlocks {
// sanity check
runtimePanic("gc: metadata array is too small")
}
// Set all block states to 'free'.
memzero(unsafe.Pointer(heapStart), metadataSize)
}
// alloc tries to find some free space on the heap, possibly doing a garbage // alloc tries to find some free space on the heap, possibly doing a garbage
// collection cycle if needed. If no space is free, it panics. // collection cycle if needed. If no space is free, it panics.
func alloc(size uintptr) unsafe.Pointer { func heapAlloc(size uintptr) unsafe.Pointer {
if size == 0 { if size == 0 {
return unsafe.Pointer(&zeroSizedAlloc) return unsafe.Pointer(&zeroSizedAlloc)
} }
@@ -275,53 +240,6 @@ func free(ptr unsafe.Pointer) {
// TODO: free blocks on request, when the compiler knows they're unused. // TODO: free blocks on request, when the compiler knows they're unused.
} }
// GC performs a garbage collection cycle.
func GC() {
if gcDebug {
println("running collection cycle...")
}
// Mark phase: mark all reachable objects, recursively.
markRoots(globalsStart, globalsEnd)
markRoots(getCurrentStackPointer(), stackTop) // assume a descending stack
// Sweep phase: free all non-marked objects and unmark marked objects for
// the next collection cycle.
sweep()
// Show how much has been sweeped, for debugging.
if gcDebug {
dumpHeap()
}
}
// markRoots reads all pointers from start to end (exclusive) and if they look
// like a heap pointer and are unmarked, marks them and scans that object as
// well (recursively). The start and end parameters must be valid pointers and
// must be aligned.
func markRoots(start, end uintptr) {
if gcDebug {
println("mark from", start, "to", end, int(end-start))
}
for addr := start; addr != end; addr += unsafe.Sizeof(addr) {
root := *(*uintptr)(unsafe.Pointer(addr))
if looksLikePointer(root) {
block := blockFromAddr(root)
head := block.findHead()
if head.state() != blockStateMark {
if gcDebug {
println("found unmarked pointer", root, "at address", addr)
}
head.setState(blockStateMark)
next := block.findNext()
// TODO: avoid recursion as much as possible
markRoots(head.address(), next.address())
}
}
}
}
// Sweep goes through all memory and frees unmarked memory. // Sweep goes through all memory and frees unmarked memory.
func sweep() { func sweep() {
freeCurrentObject := false freeCurrentObject := false
@@ -347,10 +265,8 @@ func sweep() {
} }
} }
// looksLikePointer returns whether this could be a pointer. Currently, it // addressOnHeap returns whether this address points into the heap.
// simply returns whether it lies anywhere in the heap. Go allows interior func addressOnHeap(ptr uintptr) bool {
// pointers so we can't check alignment or anything like that.
func looksLikePointer(ptr uintptr) bool {
return ptr >= poolStart && ptr < heapEnd return ptr >= poolStart && ptr < heapEnd
} }
+91
View File
@@ -0,0 +1,91 @@
// +build gc.conservative
package runtime
import (
"unsafe"
)
// Initialize the memory allocator.
// No memory may be allocated before this is called. That means the runtime and
// any packages the runtime depends upon may not allocate memory during package
// initialization.
func init() {
totalSize := heapEnd - heapStart
// Allocate some memory to keep 2 bits of information about every block.
metadataSize := totalSize / (blocksPerStateByte * bytesPerBlock)
// Align the pool.
poolStart = (heapStart + metadataSize + (bytesPerBlock - 1)) &^ (bytesPerBlock - 1)
poolEnd := heapEnd &^ (bytesPerBlock - 1)
numBlocks := (poolEnd - poolStart) / bytesPerBlock
endBlock = gcBlock(numBlocks)
if gcDebug {
println("heapStart: ", heapStart)
println("heapEnd: ", heapEnd)
println("total size: ", totalSize)
println("metadata size: ", metadataSize)
println("poolStart: ", poolStart)
println("# of blocks: ", numBlocks)
println("# of block states:", metadataSize*blocksPerStateByte)
}
if gcAsserts && metadataSize*blocksPerStateByte < numBlocks {
// sanity check
runtimePanic("gc: metadata array is too small")
}
// Set all block states to 'free'.
memzero(unsafe.Pointer(heapStart), metadataSize)
}
func alloc(size uintptr) unsafe.Pointer {
return heapAlloc(size)
}
// GC performs a garbage collection cycle.
func GC() {
if gcDebug {
println("running collection cycle...")
}
// Mark phase: mark all reachable objects, recursively.
markRoots(globalsStart, globalsEnd)
markRoots(getCurrentStackPointer(), stackTop) // assume a descending stack
// Sweep phase: free all non-marked objects and unmark marked objects for
// the next collection cycle.
sweep()
// Show how much has been sweeped, for debugging.
if gcDebug {
dumpHeap()
}
}
// markRoots reads all pointers from start to end (exclusive) and if they look
// like a heap pointer and are unmarked, marks them and scans that object as
// well (recursively). The start and end parameters must be valid pointers and
// must be aligned.
func markRoots(start, end uintptr) {
if gcDebug {
println("mark from", start, "to", end, int(end-start))
}
for addr := start; addr != end; addr += unsafe.Sizeof(addr) {
root := *(*uintptr)(unsafe.Pointer(addr))
if addressOnHeap(root) {
block := blockFromAddr(root)
head := block.findHead()
if head.state() != blockStateMark {
if gcDebug {
println("found unmarked pointer", root, "at address", addr)
}
head.setState(blockStateMark)
next := block.findNext()
// TODO: avoid recursion as much as possible
markRoots(head.address(), next.address())
}
}
}
}
@@ -1,4 +1,4 @@
// +build gc.dumb // +build gc.leaking
package runtime package runtime
@@ -30,18 +30,6 @@ func alloc(size uintptr) unsafe.Pointer {
return unsafe.Pointer(addr) return unsafe.Pointer(addr)
} }
func free(ptr unsafe.Pointer) {
// Memory is never freed.
}
func GC() { func GC() {
// No-op. // No-op.
} }
func KeepAlive(x interface{}) {
// Unimplemented. Only required with SetFinalizer().
}
func SetFinalizer(obj interface{}, finalizer interface{}) {
// Unimplemented.
}
-12
View File
@@ -12,18 +12,6 @@ import (
func alloc(size uintptr) unsafe.Pointer func alloc(size uintptr) unsafe.Pointer
func free(ptr unsafe.Pointer) {
// Nothing to free when nothing gets allocated.
}
func GC() { func GC() {
// Unimplemented. // Unimplemented.
} }
func KeepAlive(x interface{}) {
// Unimplemented. Only required with SetFinalizer().
}
func SetFinalizer(obj interface{}, finalizer interface{}) {
// Unimplemented.
}
+116
View File
@@ -0,0 +1,116 @@
// +build gc.precise
package runtime
import (
"unsafe"
)
//go:extern runtime.trackedGlobalsStart
var trackedGlobalsStart uintptr
//go:extern runtime.trackedGlobalsLength
var trackedGlobalsLength uintptr
//go:extern runtime.trackedGlobalsBitmap
var trackedGlobalsBitmap [0]uint8
// Initialize the memory allocator.
// No memory may be allocated before this is called. That means the runtime and
// any packages the runtime depends upon may not allocate memory during package
// initialization.
func init() {
totalSize := heapEnd - heapStart
// Allocate some memory to keep 2 bits of information about every block.
metadataSize := totalSize / (blocksPerStateByte * bytesPerBlock)
// Align the pool.
poolStart = (heapStart + metadataSize + (bytesPerBlock - 1)) &^ (bytesPerBlock - 1)
poolEnd := heapEnd &^ (bytesPerBlock - 1)
numBlocks := (poolEnd - poolStart) / bytesPerBlock
endBlock = gcBlock(numBlocks)
if gcDebug {
println("heapStart: ", heapStart)
println("heapEnd: ", heapEnd)
println("total size: ", totalSize)
println("metadata size: ", metadataSize)
println("poolStart: ", poolStart)
println("# of blocks: ", numBlocks)
println("# of block states:", metadataSize*blocksPerStateByte)
}
if gcAsserts && metadataSize*blocksPerStateByte < numBlocks {
// sanity check
runtimePanic("gc: metadata array is too small")
}
// Set all block states to 'free'.
memzero(unsafe.Pointer(heapStart), metadataSize)
}
func alloc(size uintptr) unsafe.Pointer {
GC()
return heapAlloc(size)
}
// GC performs a garbage collection cycle.
func GC() {
if gcDebug {
println("\nrunning collection cycle...")
}
// Mark phase: mark all reachable objects, recursively.
markGlobals()
markRoots(getCurrentStackPointer(), stackTop) // assume a descending stack
// Sweep phase: free all non-marked objects and unmark marked objects for
// the next collection cycle.
sweep()
// Show how much has been sweeped, for debugging.
if gcDebug {
dumpHeap()
}
}
//go:nobounds
func markGlobals() {
for i := uintptr(0); i < trackedGlobalsLength; i++ {
if trackedGlobalsBitmap[i/8]&(1<<(i%8)) != 0 {
addr := trackedGlobalsStart + i*unsafe.Alignof(uintptr(0))
root := *(*uintptr)(unsafe.Pointer(addr))
markRoot(addr, root)
}
}
}
// markRoots reads all pointers from start to end (exclusive) and if they look
// like a heap pointer and are unmarked, marks them and scans that object as
// well (recursively). The start and end parameters must be valid pointers and
// must be aligned.
func markRoots(start, end uintptr) {
if gcDebug {
println("mark from", start, "to", end, int(end-start))
}
for addr := start; addr != end; addr += unsafe.Sizeof(addr) {
root := *(*uintptr)(unsafe.Pointer(addr))
markRoot(addr, root)
}
}
func markRoot(addr, root uintptr) {
if addressOnHeap(root) {
block := blockFromAddr(root)
head := block.findHead()
if head.state() != blockStateMark {
if gcDebug {
println("found unmarked pointer", root, "at address", addr)
}
head.setState(blockStateMark)
next := block.findNext()
// TODO: avoid recursion as much as possible
markRoots(head.address(), next.address())
}
}
}
+46 -12
View File
@@ -60,14 +60,20 @@ func hashmapTopHash(hash uint32) uint8 {
} }
// Create a new hashmap with the given keySize and valueSize. // Create a new hashmap with the given keySize and valueSize.
func hashmapMake(keySize, valueSize uint8) *hashmap { func hashmapMake(keySize, valueSize uint8, sizeHint uintptr) *hashmap {
numBuckets := sizeHint / 8
bucketBits := uint8(0)
for numBuckets != 0 {
numBuckets /= 2
bucketBits++
}
bucketBufSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(keySize)*8 + uintptr(valueSize)*8 bucketBufSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(keySize)*8 + uintptr(valueSize)*8
bucket := alloc(bucketBufSize) buckets := alloc(bucketBufSize * (1 << bucketBits))
return &hashmap{ return &hashmap{
buckets: bucket, buckets: buckets,
keySize: keySize, keySize: keySize,
valueSize: valueSize, valueSize: valueSize,
bucketBits: 0, bucketBits: bucketBits,
} }
} }
@@ -83,13 +89,20 @@ func hashmapLen(m *hashmap) int {
// Set a specified key to a given value. Grow the map if necessary. // Set a specified key to a given value. Grow the map if necessary.
//go:nobounds //go:nobounds
func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint32, keyEqual func(x, y unsafe.Pointer, n uintptr) bool) { func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint32, keyEqual func(x, y unsafe.Pointer, n uintptr) bool) {
tophash := hashmapTopHash(hash)
if m.buckets == nil {
// No bucket was allocated yet, do so now.
m.buckets = unsafe.Pointer(hashmapInsertIntoNewBucket(m, key, value, tophash))
return
}
numBuckets := uintptr(1) << m.bucketBits numBuckets := uintptr(1) << m.bucketBits
bucketNumber := (uintptr(hash) & (numBuckets - 1)) bucketNumber := (uintptr(hash) & (numBuckets - 1))
bucketSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8 bucketSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8
bucketAddr := uintptr(m.buckets) + bucketSize*bucketNumber bucketAddr := uintptr(m.buckets) + bucketSize*bucketNumber
bucket := (*hashmapBucket)(unsafe.Pointer(bucketAddr)) bucket := (*hashmapBucket)(unsafe.Pointer(bucketAddr))
var lastBucket *hashmapBucket
tophash := hashmapTopHash(hash)
// See whether the key already exists somewhere. // See whether the key already exists somewhere.
var emptySlotKey unsafe.Pointer var emptySlotKey unsafe.Pointer
@@ -98,9 +111,9 @@ func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint3
for bucket != nil { for bucket != nil {
for i := uintptr(0); i < 8; i++ { for i := uintptr(0); i < 8; i++ {
slotKeyOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*uintptr(i) slotKeyOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*uintptr(i)
slotKey := unsafe.Pointer(bucketAddr + slotKeyOffset) slotKey := unsafe.Pointer(uintptr(unsafe.Pointer(bucket)) + slotKeyOffset)
slotValueOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*uintptr(i) slotValueOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*uintptr(i)
slotValue := unsafe.Pointer(bucketAddr + slotValueOffset) slotValue := unsafe.Pointer(uintptr(unsafe.Pointer(bucket)) + slotValueOffset)
if bucket.tophash[i] == 0 && emptySlotKey == nil { if bucket.tophash[i] == 0 && emptySlotKey == nil {
// Found an empty slot, store it for if we couldn't find an // Found an empty slot, store it for if we couldn't find an
// existing slot. // existing slot.
@@ -109,7 +122,7 @@ func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint3
emptySlotTophash = &bucket.tophash[i] emptySlotTophash = &bucket.tophash[i]
} }
if bucket.tophash[i] == tophash { if bucket.tophash[i] == tophash {
// Could be an existing value that's the same. // Could be an existing key that's the same.
if keyEqual(key, slotKey, uintptr(m.keySize)) { if keyEqual(key, slotKey, uintptr(m.keySize)) {
// found same key, replace it // found same key, replace it
memcpy(slotValue, value, uintptr(m.valueSize)) memcpy(slotValue, value, uintptr(m.valueSize))
@@ -117,16 +130,37 @@ func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint3
} }
} }
} }
lastBucket = bucket
bucket = bucket.next bucket = bucket.next
} }
if emptySlotKey != nil { if emptySlotKey == nil {
// Add a new bucket to the bucket chain.
// TODO: rebalance if necessary to avoid O(n) insert and lookup time.
lastBucket.next = (*hashmapBucket)(hashmapInsertIntoNewBucket(m, key, value, tophash))
return
}
m.count++ m.count++
memcpy(emptySlotKey, key, uintptr(m.keySize)) memcpy(emptySlotKey, key, uintptr(m.keySize))
memcpy(emptySlotValue, value, uintptr(m.valueSize)) memcpy(emptySlotValue, value, uintptr(m.valueSize))
*emptySlotTophash = tophash *emptySlotTophash = tophash
return
} }
panic("todo: hashmap: grow bucket")
// hashmapInsertIntoNewBucket creates a new bucket, inserts the given key and
// value into the bucket, and returns a pointer to this bucket.
func hashmapInsertIntoNewBucket(m *hashmap, key, value unsafe.Pointer, tophash uint8) *hashmapBucket {
bucketBufSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8
bucketBuf := alloc(bucketBufSize)
// Insert into the first slot, which is empty as it has just been allocated.
slotKeyOffset := unsafe.Sizeof(hashmapBucket{})
slotKey := unsafe.Pointer(uintptr(bucketBuf) + slotKeyOffset)
slotValueOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8
slotValue := unsafe.Pointer(uintptr(bucketBuf) + slotValueOffset)
m.count++
memcpy(slotKey, key, uintptr(m.keySize))
memcpy(slotValue, value, uintptr(m.valueSize))
bucket := (*hashmapBucket)(bucketBuf)
bucket.tophash[0] = tophash
return bucket
} }
// Get the value of a specified key, or zero the value if not found. // Get the value of a specified key, or zero the value if not found.
+4 -4
View File
@@ -17,19 +17,19 @@ func sleepWDT(period uint8) {
avr.Asm("cli") avr.Asm("cli")
avr.Asm("wdr") avr.Asm("wdr")
// Start timed sequence. // Start timed sequence.
*avr.WDTCSR |= avr.WDTCSR_WDCE | avr.WDTCSR_WDE avr.WDTCSR.SetBits(avr.WDTCSR_WDCE | avr.WDTCSR_WDE)
// Enable WDT and set new timeout // Enable WDT and set new timeout
*avr.WDTCSR = avr.WDTCSR_WDIE | avr.RegValue(period) avr.WDTCSR.SetBits(avr.WDTCSR_WDIE | period)
avr.Asm("sei") avr.Asm("sei")
// Set sleep mode to idle and enable sleep mode. // Set sleep mode to idle and enable sleep mode.
// Note: when using something other than idle, the UART won't work // Note: when using something other than idle, the UART won't work
// correctly. This needs to be fixed, though, so we can truly sleep. // correctly. This needs to be fixed, though, so we can truly sleep.
*avr.SMCR = (0 << 1) | avr.SMCR_SE avr.SMCR.Set((0 << 1) | avr.SMCR_SE)
// go to sleep // go to sleep
avr.Asm("sleep") avr.Asm("sleep")
// disable sleep // disable sleep
*avr.SMCR = 0 avr.SMCR.Set(0)
} }
+34
View File
@@ -0,0 +1,34 @@
// Package volatile provides definitions for volatile loads and stores. These
// are implemented as compiler builtins.
//
// The load operations load a volatile value. The store operations store to a
// volatile value. The compiler will emit exactly one load or store operation
// when possible and will not reorder volatile operations. However, the compiler
// may move other operations across load/store operations, so make sure that all
// relevant loads/stores are done in a volatile way if this is a problem.
//
// These loads and stores are commonly used to read/write values from memory
// mapped peripheral devices. They do not provide atomicity, use the sync/atomic
// package for that.
//
// For more details: https://llvm.org/docs/LangRef.html#volatile-memory-accesses
// and https://blog.regehr.org/archives/28.
package volatile
// LoadUint8 loads the volatile value *addr.
func LoadUint8(addr *uint8) (val uint8)
// LoadUint16 loads the volatile value *addr.
func LoadUint16(addr *uint16) (val uint16)
// LoadUint32 loads the volatile value *addr.
func LoadUint32(addr *uint32) (val uint32)
// StoreUint8 stores val to the volatile value *addr.
func StoreUint8(addr *uint8, val uint8)
// StoreUint16 stores val to the volatile value *addr.
func StoreUint16(addr *uint16, val uint16)
// StoreUint32 stores val to the volatile value *addr.
func StoreUint32(addr *uint32, val uint32)
+1 -1
View File
@@ -3,7 +3,7 @@
"goos": "linux", "goos": "linux",
"goarch": "arm", "goarch": "arm",
"compiler": "clang", "compiler": "clang",
"gc": "marksweep", "gc": "precise",
"linker": "ld.lld", "linker": "ld.lld",
"rtlib": "compiler-rt", "rtlib": "compiler-rt",
"cflags": [ "cflags": [
+22
View File
@@ -4,6 +4,22 @@ type Thing struct {
name string name string
} }
type ThingOption func(*Thing)
func WithName(name string) ThingOption {
return func(t *Thing) {
t.name = name
}
}
func NewThing(opts ...ThingOption) *Thing {
t := &Thing{}
for _, opt := range opts {
opt(t)
}
return t
}
func (t Thing) String() string { func (t Thing) String() string {
return t.name return t.name
} }
@@ -36,6 +52,12 @@ func main() {
runFunc(func(i int) { runFunc(func(i int) {
println("inside fp closure:", thing.String(), i) println("inside fp closure:", thing.String(), i)
}, 3) }, 3)
// functional arguments
thingFunctionalArgs1 := NewThing()
thingFunctionalArgs1.Print("functional args 1")
thingFunctionalArgs2 := NewThing(WithName("named thing"))
thingFunctionalArgs2.Print("functional args 2")
} }
func runFunc(f func(int), arg int) { func runFunc(f func(int), arg int) {
+2
View File
@@ -7,3 +7,5 @@ Thing.Print: foo arg: bar
bound method: foo bound method: foo
thing inside closure: foo thing inside closure: foo
inside fp closure: foo 3 inside fp closure: foo 3
Thing.Print: arg: functional args 1
Thing.Print: named thing arg: functional args 2
+6
View File
@@ -0,0 +1,6 @@
package main
// Make sure CGo supports multiple files.
// int fortytwo(void);
import "C"
+6
View File
@@ -7,6 +7,8 @@ int mul(int, int);
*/ */
import "C" import "C"
import "C"
import "unsafe" import "unsafe"
func main() { func main() {
@@ -18,6 +20,10 @@ func main() {
var y C.longlong = -(1 << 40) var y C.longlong = -(1 << 40)
println("longlong:", y) println("longlong:", y)
println("global:", C.global) println("global:", C.global)
println("defined ints:", C.CONST_INT, C.CONST_INT2)
println("defined floats:", C.CONST_FLOAT, C.CONST_FLOAT2)
println("defined string:", C.CONST_STRING)
println("defined char:", C.CONST_CHAR)
var ptr C.intPointer var ptr C.intPointer
var n C.int = 15 var n C.int = 15
ptr = C.intPointer(&n) ptr = C.intPointer(&n)
+7
View File
@@ -10,6 +10,13 @@ int doCallback(int a, int b, binop_t cb);
typedef int * intPointer; typedef int * intPointer;
void store(int value, int *ptr); void store(int value, int *ptr);
# define CONST_INT 5
# define CONST_INT2 5llu
# define CONST_FLOAT 5.8
# define CONST_FLOAT2 5.8f
# define CONST_CHAR 'c'
# define CONST_STRING "defined string"
// this signature should not be included by CGo // this signature should not be included by CGo
void unusedFunction2(int x, __builtin_va_list args); void unusedFunction2(int x, __builtin_va_list args);
+4
View File
@@ -4,6 +4,10 @@ myint: 3 5
myint size: 2 myint size: 2
longlong: -1099511627776 longlong: -1099511627776
global: 3 global: 3
defined ints: 5 5
defined floats: +5.800000e+000 +5.800000e+000
defined string: defined string
defined char: 99
15: 15 15: 15
25: 25 25: 25
callback 1: 50 callback 1: 50
+1 -1
View File
@@ -4,7 +4,7 @@ import "time"
func main() { func main() {
ch := make(chan int) ch := make(chan int)
println("len, cap of channel:", len(ch), cap(ch)) println("len, cap of channel:", len(ch), cap(ch), ch == nil)
go sender(ch) go sender(ch)
n, ok := <-ch n, ok := <-ch
+1 -1
View File
@@ -1,4 +1,4 @@
len, cap of channel: 0 0 len, cap of channel: 0 0 false
recv from open channel: 1 true recv from open channel: 1 true
received num: 2 received num: 2
received num: 3 received num: 3
+32
View File
@@ -47,6 +47,18 @@ func main() {
println(testMapArrayKey[arrKey]) println(testMapArrayKey[arrKey])
testMapArrayKey[arrKey] = 5555 testMapArrayKey[arrKey] = 5555
println(testMapArrayKey[arrKey]) println(testMapArrayKey[arrKey])
// test preallocated map
squares := make(map[int]int, 200)
testBigMap(squares, 100)
println("tested preallocated map")
// test growing maps
squares = make(map[int]int, 0)
testBigMap(squares, 10)
squares = make(map[int]int, 20)
testBigMap(squares, 40)
println("tested growing of a map")
} }
func readMap(m map[string]int, key string) { func readMap(m map[string]int, key string) {
@@ -56,7 +68,27 @@ func readMap(m map[string]int, key string) {
println(" ", k, "=", v) println(" ", k, "=", v)
} }
} }
func lookup(m map[string]int, key string) { func lookup(m map[string]int, key string) {
value, ok := m[key] value, ok := m[key]
println("lookup with comma-ok:", key, value, ok) println("lookup with comma-ok:", key, value, ok)
} }
func testBigMap(squares map[int]int, n int) {
for i := 0; i < n; i++ {
if len(squares) != i {
println("unexpected length:", len(squares), "at i =", i)
}
squares[i] = i*i
for j := 0; j <= i; j++ {
if v, ok := squares[j]; !ok || v != j*j {
if !ok {
println("key not found in squares map:", j)
} else {
println("unexpected value read back from squares map:", j, v)
}
return
}
}
}
}
+2
View File
@@ -54,3 +54,5 @@ true false 0
42 42
4321 4321
5555 5555
tested preallocated map
tested growing of a map
+46 -4
View File
@@ -151,12 +151,54 @@ def writeGo(outdir, device):
// {description} // {description}
package {pkgName} package {pkgName}
import "unsafe" import (
"runtime/volatile"
"unsafe"
)
// Special type that causes loads/stores to be volatile (necessary for // Special type that causes loads/stores to be volatile (necessary for
// memory-mapped registers). // memory-mapped registers).
//go:volatile type Register8 struct {{
type RegValue uint8 Reg uint8
}}
// Get returns the value in the register. It is the volatile equivalent of:
//
// *r.Reg
//
//go:inline
func (r *Register8) Get() uint8 {{
return volatile.LoadUint8(&r.Reg)
}}
// Set updates the register value. It is the volatile equivalent of:
//
// *r.Reg = value
//
//go:inline
func (r *Register8) Set(value uint8) {{
volatile.StoreUint8(&r.Reg, value)
}}
// SetBits reads the register, sets the given bits, and writes it back. It is
// the volatile equivalent of:
//
// r.Reg |= value
//
//go:inline
func (r *Register8) SetBits(value uint8) {{
volatile.StoreUint8(&r.Reg, volatile.LoadUint8(&r.Reg) | value)
}}
// ClearBits reads the register, clears the given bits, and writes it back. It
// is the volatile equivalent of:
//
// r.Reg &^= value
//
//go:inline
func (r *Register8) ClearBits(value uint8) {{
volatile.StoreUint8(&r.Reg, volatile.LoadUint8(&r.Reg) &^ value)
}}
// Some information about this device. // Some information about this device.
const ( const (
@@ -179,7 +221,7 @@ const (
out.write('\n\t// {description}\n'.format(**peripheral)) out.write('\n\t// {description}\n'.format(**peripheral))
for register in peripheral['registers']: for register in peripheral['registers']:
for variant in register['variants']: for variant in register['variants']:
out.write('\t{name} = (*RegValue)(unsafe.Pointer(uintptr(0x{address:x})))\n'.format(**variant)) out.write('\t{name} = (*Register8)(unsafe.Pointer(uintptr(0x{address:x})))\n'.format(**variant))
out.write(')\n') out.write(')\n')
for peripheral in device.peripherals: for peripheral in device.peripherals: