mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-26 14:48:40 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a1e69bbc13 | |||
| 1b4d71bd3d | |||
| 43b3bb6e83 | |||
| e0cf74e638 | |||
| 6f6afb0515 | |||
| 397b90753c | |||
| 3c2639ad55 | |||
| 371c468e8e | |||
| 0a40219680 | |||
| c981f14e61 | |||
| 763b9d7d10 | |||
| 55fc7b904a | |||
| 17c42810d0 | |||
| 064d001550 | |||
| a4cd3bb77c | |||
| 8d3f19bc84 | |||
| d90f1947d9 | |||
| 11567c62d4 | |||
| 4619207f99 | |||
| 99587fe073 |
@@ -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=stm32f4disco examples/blinky1
|
||||
- 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/main
|
||||
test-linux:
|
||||
|
||||
@@ -32,7 +32,7 @@ CGO_LDFLAGS=-L$(LLVM_BUILDDIR)/lib $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUIL
|
||||
clean:
|
||||
@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:
|
||||
@gofmt -l -w $(FMT_PATHS)
|
||||
fmt-check:
|
||||
|
||||
+254
-139
@@ -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
|
||||
// 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 (
|
||||
"go/ast"
|
||||
@@ -13,27 +21,38 @@ import (
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
)
|
||||
|
||||
// fileInfo holds all Cgo-related information of a given *ast.File.
|
||||
type fileInfo struct {
|
||||
*ast.File
|
||||
*Package
|
||||
filename string
|
||||
functions map[string]*functionInfo
|
||||
globals map[string]*globalInfo
|
||||
typedefs map[string]*typedefInfo
|
||||
elaboratedTypes map[string]ast.Expr
|
||||
importCPos token.Pos
|
||||
// cgoPackage holds all CGo-related information of a package.
|
||||
type cgoPackage struct {
|
||||
generated *ast.File
|
||||
generatedPos token.Pos
|
||||
errors []error
|
||||
dir string
|
||||
fset *token.FileSet
|
||||
tokenFiles map[string]*token.File
|
||||
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.
|
||||
type functionInfo struct {
|
||||
args []paramInfo
|
||||
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 {
|
||||
name string
|
||||
typeExpr ast.Expr
|
||||
@@ -42,11 +61,20 @@ type paramInfo struct {
|
||||
// typedefInfo contains information about a single typedef in C.
|
||||
type typedefInfo struct {
|
||||
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.
|
||||
type globalInfo struct {
|
||||
typeExpr ast.Expr
|
||||
pos token.Pos
|
||||
}
|
||||
|
||||
// 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",
|
||||
}
|
||||
|
||||
// cgoBuiltinAliases are handled specially because they only exist on the Go
|
||||
// side of CGo, not on the CGo (they're prefixed with "_Cgo_" there).
|
||||
var cgoBuiltinAliases = map[string]struct{}{
|
||||
// builtinAliases are handled specially because they only exist on the Go side
|
||||
// of CGo, not on the CGo side (they're prefixed with "_Cgo_" there).
|
||||
var builtinAliases = map[string]struct{}{
|
||||
"char": struct{}{},
|
||||
"schar": struct{}{},
|
||||
"uchar": struct{}{},
|
||||
@@ -96,103 +124,146 @@ typedef long long _Cgo_longlong;
|
||||
typedef unsigned long long _Cgo_ulonglong;
|
||||
`
|
||||
|
||||
// processCgo extracts the `import "C"` statement from the AST, parses the
|
||||
// comment with libclang, and modifies the AST to use this information.
|
||||
func (p *Package) processCgo(filename string, f *ast.File, cflags []string) []error {
|
||||
info := &fileInfo{
|
||||
File: f,
|
||||
Package: p,
|
||||
filename: filename,
|
||||
functions: map[string]*functionInfo{},
|
||||
globals: map[string]*globalInfo{},
|
||||
typedefs: map[string]*typedefInfo{},
|
||||
elaboratedTypes: map[string]ast.Expr{},
|
||||
// Process extracts `import "C"` statements from the AST, parses the comment
|
||||
// with libclang, and modifies the AST to use this information. It returns a
|
||||
// newly created *ast.File that should be added to the list of to-be-parsed
|
||||
// files. If there is one or more error, it returns these in the []error slice
|
||||
// but still modifies the AST.
|
||||
func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []error) {
|
||||
p := &cgoPackage{
|
||||
dir: dir,
|
||||
fset: fset,
|
||||
tokenFiles: map[string]*token.File{},
|
||||
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.
|
||||
f = astutil.Apply(f, info.findMissingCGoNames, nil).(*ast.File)
|
||||
for name := range cgoBuiltinAliases {
|
||||
info.missingSymbols["_Cgo_"+name] = struct{}{}
|
||||
for _, f := range files {
|
||||
astutil.Apply(f, p.findMissingCGoNames, nil)
|
||||
}
|
||||
for name := range builtinAliases {
|
||||
p.missingSymbols["_Cgo_"+name] = struct{}{}
|
||||
}
|
||||
|
||||
// Find `import "C"` statements in the file.
|
||||
for i := 0; i < len(f.Decls); i++ {
|
||||
decl := f.Decls[i]
|
||||
genDecl, ok := decl.(*ast.GenDecl)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if len(genDecl.Specs) != 1 {
|
||||
continue
|
||||
}
|
||||
spec, ok := genDecl.Specs[0].(*ast.ImportSpec)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
path, err := strconv.Unquote(spec.Path.Value)
|
||||
if err != nil {
|
||||
panic("could not parse import path: " + err.Error())
|
||||
}
|
||||
if path != "C" {
|
||||
continue
|
||||
}
|
||||
cgoComment := genDecl.Doc.Text()
|
||||
for _, f := range files {
|
||||
for i := 0; i < len(f.Decls); i++ {
|
||||
decl := f.Decls[i]
|
||||
genDecl, ok := decl.(*ast.GenDecl)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if len(genDecl.Specs) != 1 {
|
||||
continue
|
||||
}
|
||||
spec, ok := genDecl.Specs[0].(*ast.ImportSpec)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
path, err := strconv.Unquote(spec.Path.Value)
|
||||
if err != nil {
|
||||
panic("could not parse import path: " + err.Error())
|
||||
}
|
||||
if path != "C" {
|
||||
continue
|
||||
}
|
||||
cgoComment := genDecl.Doc.Text()
|
||||
|
||||
// Stored for later use by generated functions, to use a somewhat sane
|
||||
// source location.
|
||||
info.importCPos = spec.Path.ValuePos
|
||||
pos := genDecl.Pos()
|
||||
if genDecl.Doc != nil {
|
||||
pos = genDecl.Doc.Pos()
|
||||
}
|
||||
position := fset.PositionFor(pos, true)
|
||||
p.parseFragment(cgoComment+cgoTypes, cflags, position.Filename, position.Line)
|
||||
|
||||
pos := info.fset.PositionFor(genDecl.Doc.Pos(), true)
|
||||
errs := info.parseFragment(cgoComment+cgoTypes, cflags, pos.Filename, pos.Line)
|
||||
if errs != nil {
|
||||
return errs
|
||||
// Remove this import declaration.
|
||||
f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
|
||||
i--
|
||||
}
|
||||
|
||||
// Remove this import declaration.
|
||||
f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
|
||||
i--
|
||||
// Print the AST, for debugging.
|
||||
//ast.Print(fset, f)
|
||||
}
|
||||
|
||||
// Print the AST, for debugging.
|
||||
//ast.Print(p.fset, f)
|
||||
|
||||
// Declare functions found by libclang.
|
||||
info.addFuncDecls()
|
||||
p.addFuncDecls()
|
||||
|
||||
// Declare stub function pointer values found by libclang.
|
||||
info.addFuncPtrDecls()
|
||||
p.addFuncPtrDecls()
|
||||
|
||||
// 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).
|
||||
info.addTypeAliases()
|
||||
p.addTypeAliases()
|
||||
|
||||
// Add type declarations for C types, declared using typedef in C.
|
||||
info.addTypedefs()
|
||||
p.addTypedefs()
|
||||
|
||||
// Add elaborated types for C structs and unions.
|
||||
info.addElaboratedTypes()
|
||||
p.addElaboratedTypes()
|
||||
|
||||
// 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
|
||||
// comment above the `import "C"` statement.
|
||||
func (info *fileInfo) addFuncDecls() {
|
||||
// TODO: replace all uses of importCPos with the real locations from
|
||||
// libclang.
|
||||
names := make([]string, 0, len(info.functions))
|
||||
for name := range info.functions {
|
||||
func (p *cgoPackage) addFuncDecls() {
|
||||
names := make([]string, 0, len(p.functions))
|
||||
for name := range p.functions {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
fn := info.functions[name]
|
||||
fn := p.functions[name]
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Fun,
|
||||
Name: "C." + name,
|
||||
@@ -200,16 +271,16 @@ func (info *fileInfo) addFuncDecls() {
|
||||
args := make([]*ast.Field, len(fn.args))
|
||||
decl := &ast.FuncDecl{
|
||||
Name: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
NamePos: fn.pos,
|
||||
Name: "C." + name,
|
||||
Obj: obj,
|
||||
},
|
||||
Type: &ast.FuncType{
|
||||
Func: info.importCPos,
|
||||
Func: fn.pos,
|
||||
Params: &ast.FieldList{
|
||||
Opening: info.importCPos,
|
||||
Opening: fn.pos,
|
||||
List: args,
|
||||
Closing: info.importCPos,
|
||||
Closing: fn.pos,
|
||||
},
|
||||
Results: fn.results,
|
||||
},
|
||||
@@ -219,7 +290,7 @@ func (info *fileInfo) addFuncDecls() {
|
||||
args[i] = &ast.Field{
|
||||
Names: []*ast.Ident{
|
||||
&ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
NamePos: fn.pos,
|
||||
Name: arg.name,
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
@@ -231,7 +302,7 @@ func (info *fileInfo) addFuncDecls() {
|
||||
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
|
||||
// // ...
|
||||
// )
|
||||
func (info *fileInfo) addFuncPtrDecls() {
|
||||
if len(info.functions) == 0 {
|
||||
func (p *cgoPackage) addFuncPtrDecls() {
|
||||
if len(p.functions) == 0 {
|
||||
return
|
||||
}
|
||||
gen := &ast.GenDecl{
|
||||
TokPos: info.importCPos,
|
||||
TokPos: token.NoPos,
|
||||
Tok: token.VAR,
|
||||
Lparen: info.importCPos,
|
||||
Rparen: info.importCPos,
|
||||
Lparen: token.NoPos,
|
||||
Rparen: token.NoPos,
|
||||
}
|
||||
names := make([]string, 0, len(info.functions))
|
||||
for name := range info.functions {
|
||||
names := make([]string, 0, len(p.functions))
|
||||
for name := range p.functions {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
fn := p.functions[name]
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Typ,
|
||||
Name: "C." + name + "$funcaddr",
|
||||
}
|
||||
valueSpec := &ast.ValueSpec{
|
||||
Names: []*ast.Ident{&ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
NamePos: fn.pos,
|
||||
Name: "C." + name + "$funcaddr",
|
||||
Obj: obj,
|
||||
}},
|
||||
Type: &ast.SelectorExpr{
|
||||
X: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
NamePos: fn.pos,
|
||||
Name: "unsafe",
|
||||
},
|
||||
Sel: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
NamePos: fn.pos,
|
||||
Name: "Pointer",
|
||||
},
|
||||
},
|
||||
@@ -284,7 +356,50 @@ func (info *fileInfo) addFuncPtrDecls() {
|
||||
obj.Decl = 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.
|
||||
@@ -295,30 +410,30 @@ func (info *fileInfo) addFuncPtrDecls() {
|
||||
// C.globalBool bool
|
||||
// // ...
|
||||
// )
|
||||
func (info *fileInfo) addVarDecls() {
|
||||
if len(info.globals) == 0 {
|
||||
func (p *cgoPackage) addVarDecls() {
|
||||
if len(p.globals) == 0 {
|
||||
return
|
||||
}
|
||||
gen := &ast.GenDecl{
|
||||
TokPos: info.importCPos,
|
||||
TokPos: token.NoPos,
|
||||
Tok: token.VAR,
|
||||
Lparen: info.importCPos,
|
||||
Rparen: info.importCPos,
|
||||
Lparen: token.NoPos,
|
||||
Rparen: token.NoPos,
|
||||
}
|
||||
names := make([]string, 0, len(info.globals))
|
||||
for name := range info.globals {
|
||||
names := make([]string, 0, len(p.globals))
|
||||
for name := range p.globals {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
global := info.globals[name]
|
||||
global := p.globals[name]
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Typ,
|
||||
Kind: ast.Var,
|
||||
Name: "C." + name,
|
||||
}
|
||||
valueSpec := &ast.ValueSpec{
|
||||
Names: []*ast.Ident{&ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
NamePos: global.pos,
|
||||
Name: "C." + name,
|
||||
Obj: obj,
|
||||
}},
|
||||
@@ -327,7 +442,7 @@ func (info *fileInfo) addVarDecls() {
|
||||
obj.Decl = 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.
|
||||
@@ -338,17 +453,17 @@ func (info *fileInfo) addVarDecls() {
|
||||
// C.int16_t = int16
|
||||
// // ...
|
||||
// )
|
||||
func (info *fileInfo) addTypeAliases() {
|
||||
func (p *cgoPackage) addTypeAliases() {
|
||||
aliasKeys := make([]string, 0, len(cgoAliases))
|
||||
for key := range cgoAliases {
|
||||
aliasKeys = append(aliasKeys, key)
|
||||
}
|
||||
sort.Strings(aliasKeys)
|
||||
gen := &ast.GenDecl{
|
||||
TokPos: info.importCPos,
|
||||
TokPos: token.NoPos,
|
||||
Tok: token.TYPE,
|
||||
Lparen: info.importCPos,
|
||||
Rparen: info.importCPos,
|
||||
Lparen: token.NoPos,
|
||||
Rparen: token.NoPos,
|
||||
}
|
||||
for _, typeName := range aliasKeys {
|
||||
goTypeName := cgoAliases[typeName]
|
||||
@@ -358,37 +473,37 @@ func (info *fileInfo) addTypeAliases() {
|
||||
}
|
||||
typeSpec := &ast.TypeSpec{
|
||||
Name: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
NamePos: token.NoPos,
|
||||
Name: typeName,
|
||||
Obj: obj,
|
||||
},
|
||||
Assign: info.importCPos,
|
||||
Assign: p.generatedPos,
|
||||
Type: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
NamePos: token.NoPos,
|
||||
Name: goTypeName,
|
||||
},
|
||||
}
|
||||
obj.Decl = 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() {
|
||||
if len(info.typedefs) == 0 {
|
||||
func (p *cgoPackage) addTypedefs() {
|
||||
if len(p.typedefs) == 0 {
|
||||
return
|
||||
}
|
||||
gen := &ast.GenDecl{
|
||||
TokPos: info.importCPos,
|
||||
TokPos: token.NoPos,
|
||||
Tok: token.TYPE,
|
||||
}
|
||||
names := make([]string, 0, len(info.typedefs))
|
||||
for name := range info.typedefs {
|
||||
names := make([]string, 0, len(p.typedefs))
|
||||
for name := range p.typedefs {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
typedef := info.typedefs[name]
|
||||
typedef := p.typedefs[name]
|
||||
typeName := "C." + name
|
||||
isAlias := true
|
||||
if strings.HasPrefix(name, "_Cgo_") {
|
||||
@@ -405,19 +520,19 @@ func (info *fileInfo) addTypedefs() {
|
||||
}
|
||||
typeSpec := &ast.TypeSpec{
|
||||
Name: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
NamePos: typedef.pos,
|
||||
Name: typeName,
|
||||
Obj: obj,
|
||||
},
|
||||
Type: typedef.typeExpr,
|
||||
}
|
||||
if isAlias {
|
||||
typeSpec.Assign = info.importCPos
|
||||
typeSpec.Assign = typedef.pos
|
||||
}
|
||||
obj.Decl = 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
|
||||
@@ -425,21 +540,21 @@ func (info *fileInfo) addTypedefs() {
|
||||
//
|
||||
// See also:
|
||||
// https://en.cppreference.com/w/cpp/language/elaborated_type_specifier
|
||||
func (info *fileInfo) addElaboratedTypes() {
|
||||
if len(info.elaboratedTypes) == 0 {
|
||||
func (p *cgoPackage) addElaboratedTypes() {
|
||||
if len(p.elaboratedTypes) == 0 {
|
||||
return
|
||||
}
|
||||
gen := &ast.GenDecl{
|
||||
TokPos: info.importCPos,
|
||||
TokPos: token.NoPos,
|
||||
Tok: token.TYPE,
|
||||
}
|
||||
names := make([]string, 0, len(info.elaboratedTypes))
|
||||
for name := range info.elaboratedTypes {
|
||||
names := make([]string, 0, len(p.elaboratedTypes))
|
||||
for name := range p.elaboratedTypes {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
typ := info.elaboratedTypes[name]
|
||||
typ := p.elaboratedTypes[name]
|
||||
typeName := "C." + name
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Typ,
|
||||
@@ -447,22 +562,22 @@ func (info *fileInfo) addElaboratedTypes() {
|
||||
}
|
||||
typeSpec := &ast.TypeSpec{
|
||||
Name: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
NamePos: typ.pos,
|
||||
Name: typeName,
|
||||
Obj: obj,
|
||||
},
|
||||
Type: typ,
|
||||
Type: typ.typeExpr,
|
||||
}
|
||||
obj.Decl = 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
|
||||
// these symbols are extracted from the parsed C AST and converted to the Go
|
||||
// equivalent.
|
||||
func (info *fileInfo) findMissingCGoNames(cursor *astutil.Cursor) bool {
|
||||
func (p *cgoPackage) findMissingCGoNames(cursor *astutil.Cursor) bool {
|
||||
switch node := cursor.Node().(type) {
|
||||
case *ast.SelectorExpr:
|
||||
x, ok := node.X.(*ast.Ident)
|
||||
@@ -471,10 +586,10 @@ func (info *fileInfo) findMissingCGoNames(cursor *astutil.Cursor) bool {
|
||||
}
|
||||
if x.Name == "C" {
|
||||
name := node.Sel.Name
|
||||
if _, ok := cgoBuiltinAliases[name]; ok {
|
||||
if _, ok := builtinAliases[name]; ok {
|
||||
name = "_Cgo_" + name
|
||||
}
|
||||
info.missingSymbols[name] = struct{}{}
|
||||
p.missingSymbols[name] = struct{}{}
|
||||
}
|
||||
}
|
||||
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
|
||||
// used in the middle of a name) so in practice all C identifiers live in a
|
||||
// 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) {
|
||||
case *ast.CallExpr:
|
||||
fun, ok := node.Fun.(*ast.SelectorExpr)
|
||||
@@ -495,7 +610,7 @@ func (info *fileInfo) walker(cursor *astutil.Cursor) bool {
|
||||
if !ok {
|
||||
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{
|
||||
NamePos: x.NamePos,
|
||||
Name: "C." + fun.Sel.Name,
|
||||
@@ -508,7 +623,7 @@ func (info *fileInfo) walker(cursor *astutil.Cursor) bool {
|
||||
}
|
||||
if x.Name == "C" {
|
||||
name := "C." + node.Sel.Name
|
||||
if _, ok := info.functions[node.Sel.Name]; ok {
|
||||
if _, ok := p.functions[node.Sel.Name]; ok {
|
||||
name += "$funcaddr"
|
||||
}
|
||||
cursor.Replace(&ast.Ident{
|
||||
@@ -1,4 +1,4 @@
|
||||
package loader
|
||||
package cgo
|
||||
|
||||
// This file parses a fragment of C with libclang and stores the result for AST
|
||||
// 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);
|
||||
GoCXCursor tinygo_clang_Cursor_getArgument(GoCXCursor c, unsigned i);
|
||||
CXSourceLocation tinygo_clang_getCursorLocation(GoCXCursor c);
|
||||
CXSourceRange tinygo_clang_getCursorExtent(GoCXCursor c);
|
||||
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(GoCXCursor c);
|
||||
|
||||
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"
|
||||
|
||||
// refMap stores references to types, used for clang_visitChildren.
|
||||
var refMap RefMap
|
||||
// storedRefs stores references to types, used for clang_visitChildren.
|
||||
var storedRefs refMap
|
||||
|
||||
var diagnosticSeverity = [...]string{
|
||||
C.CXDiagnostic_Ignored: "ignored",
|
||||
@@ -65,7 +66,7 @@ var diagnosticSeverity = [...]string{
|
||||
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)
|
||||
defer C.clang_disposeIndex(index)
|
||||
|
||||
@@ -101,7 +102,7 @@ func (info *fileInfo) parseFragment(fragment string, cflags []string, posFilenam
|
||||
filenameC,
|
||||
(**C.char)(cmdargsC), C.int(len(cflags)), // command line args
|
||||
&unsavedFile, 1, // unsaved files
|
||||
C.CXTranslationUnit_None,
|
||||
C.CXTranslationUnit_DetailedPreprocessingRecord,
|
||||
&unit)
|
||||
if errCode != 0 {
|
||||
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)
|
||||
|
||||
if numDiagnostics := int(C.clang_getNumDiagnostics(unit)); numDiagnostics != 0 {
|
||||
errs := []error{}
|
||||
addDiagnostic := func(diagnostic C.CXDiagnostic) {
|
||||
spelling := getString(C.clang_getDiagnosticSpelling(diagnostic))
|
||||
severity := diagnosticSeverity[C.clang_getDiagnosticSeverity(diagnostic)]
|
||||
@@ -121,12 +121,12 @@ func (info *fileInfo) parseFragment(fragment string, cflags []string, posFilenam
|
||||
filename := getString(libclangFilename)
|
||||
if filepath.IsAbs(filename) {
|
||||
// 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 {
|
||||
filename = relpath
|
||||
}
|
||||
}
|
||||
errs = append(errs, &scanner.Error{
|
||||
p.errors = append(p.errors, &scanner.Error{
|
||||
Pos: token.Position{
|
||||
Filename: filename,
|
||||
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)))
|
||||
}
|
||||
}
|
||||
return errs
|
||||
return
|
||||
}
|
||||
|
||||
ref := refMap.Put(info)
|
||||
defer refMap.Remove(ref)
|
||||
ref := storedRefs.Put(p)
|
||||
defer storedRefs.Remove(ref)
|
||||
cursor := C.tinygo_clang_getTranslationUnitCursor(unit)
|
||||
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_globals_visitor), C.CXClientData(ref))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//export tinygo_clang_globals_visitor
|
||||
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)
|
||||
pos := info.getCursorPosition(c)
|
||||
pos := p.getCursorPosition(c)
|
||||
switch kind {
|
||||
case C.CXCursor_FunctionDecl:
|
||||
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
||||
if _, required := info.missingSymbols[name]; !required {
|
||||
if _, required := p.missingSymbols[name]; !required {
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
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
|
||||
}
|
||||
numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c))
|
||||
fn := &functionInfo{}
|
||||
info.functions[name] = fn
|
||||
fn := &functionInfo{
|
||||
pos: pos,
|
||||
}
|
||||
p.functions[name] = fn
|
||||
for i := 0; i < numArgs; i++ {
|
||||
arg := C.tinygo_clang_Cursor_getArgument(c, C.uint(i))
|
||||
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{
|
||||
name: argName,
|
||||
typeExpr: info.makeASTType(argType, pos),
|
||||
typeExpr: p.makeASTType(argType, pos),
|
||||
})
|
||||
}
|
||||
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{
|
||||
List: []*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:
|
||||
typ := C.tinygo_clang_getCursorType(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
|
||||
}
|
||||
info.makeASTType(typ, pos)
|
||||
p.makeASTType(typ, pos)
|
||||
case C.CXCursor_TypedefDecl:
|
||||
typedefType := C.tinygo_clang_getCursorType(c)
|
||||
name := getString(C.clang_getTypedefName(typedefType))
|
||||
if _, required := info.missingSymbols[name]; !required {
|
||||
if _, required := p.missingSymbols[name]; !required {
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
info.makeASTType(typedefType, pos)
|
||||
p.makeASTType(typedefType, pos)
|
||||
case C.CXCursor_VarDecl:
|
||||
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
||||
if _, required := info.missingSymbols[name]; !required {
|
||||
if _, required := p.missingSymbols[name]; !required {
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
cursorType := C.tinygo_clang_getCursorType(c)
|
||||
info.globals[name] = &globalInfo{
|
||||
typeExpr: info.makeASTType(cursorType, pos),
|
||||
p.globals[name] = globalInfo{
|
||||
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
|
||||
@@ -234,7 +315,7 @@ func getString(clangString C.CXString) (s string) {
|
||||
// getCursorPosition returns a usable token.Pos from a libclang cursor. If the
|
||||
// file for this cursor has not been seen before, it is read from libclang
|
||||
// (which already has the file in memory) and added to the token.FileSet.
|
||||
func (info *fileInfo) getCursorPosition(cursor C.GoCXCursor) token.Pos {
|
||||
func (p *cgoPackage) getCursorPosition(cursor C.GoCXCursor) token.Pos {
|
||||
location := C.tinygo_clang_getCursorLocation(cursor)
|
||||
var file C.CXFile
|
||||
var line C.unsigned
|
||||
@@ -246,7 +327,7 @@ func (info *fileInfo) getCursorPosition(cursor C.GoCXCursor) token.Pos {
|
||||
return token.NoPos
|
||||
}
|
||||
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
|
||||
// now by reading the file from libclang.
|
||||
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)
|
||||
}
|
||||
}
|
||||
f := info.fset.AddFile(filename, -1, int(size))
|
||||
f := p.fset.AddFile(filename, -1, int(size))
|
||||
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,
|
||||
// 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
|
||||
switch typ.kind {
|
||||
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{
|
||||
Star: pos,
|
||||
X: info.makeASTType(pointeeType, pos),
|
||||
X: p.makeASTType(pointeeType, pos),
|
||||
}
|
||||
case C.CXType_ConstantArray:
|
||||
return &ast.ArrayType{
|
||||
@@ -339,7 +420,7 @@ func (info *fileInfo) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
|
||||
Kind: token.INT,
|
||||
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:
|
||||
// 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:
|
||||
name := getString(C.clang_getTypedefName(typ))
|
||||
if _, ok := info.typedefs[name]; !ok {
|
||||
info.typedefs[name] = nil // don't recurse
|
||||
if _, ok := p.typedefs[name]; !ok {
|
||||
p.typedefs[name] = nil // don't recurse
|
||||
c := C.tinygo_clang_getTypeDeclaration(typ)
|
||||
underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(c)
|
||||
expr := info.makeASTType(underlyingType, pos)
|
||||
expr := p.makeASTType(underlyingType, pos)
|
||||
if strings.HasPrefix(name, "_Cgo_") {
|
||||
expr := expr.(*ast.Ident)
|
||||
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,
|
||||
pos: pos,
|
||||
}
|
||||
}
|
||||
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)
|
||||
switch underlying.kind {
|
||||
case C.CXType_Record:
|
||||
return info.makeASTType(underlying, pos)
|
||||
return p.makeASTType(underlying, pos)
|
||||
default:
|
||||
panic("unknown elaborated type")
|
||||
}
|
||||
@@ -434,23 +516,26 @@ func (info *fileInfo) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
|
||||
default:
|
||||
panic("unknown record declaration")
|
||||
}
|
||||
if _, ok := info.elaboratedTypes[cgoName]; !ok {
|
||||
info.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion)
|
||||
if _, ok := p.elaboratedTypes[cgoName]; !ok {
|
||||
p.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion)
|
||||
fieldList := &ast.FieldList{
|
||||
Opening: pos,
|
||||
Closing: pos,
|
||||
}
|
||||
ref := refMap.Put(struct {
|
||||
ref := storedRefs.Put(struct {
|
||||
fieldList *ast.FieldList
|
||||
info *fileInfo
|
||||
}{fieldList, info})
|
||||
defer refMap.Remove(ref)
|
||||
pkg *cgoPackage
|
||||
}{fieldList, p})
|
||||
defer storedRefs.Remove(ref)
|
||||
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref))
|
||||
switch C.tinygo_clang_getCursorKind(cursor) {
|
||||
case C.CXCursor_StructDecl:
|
||||
info.elaboratedTypes[cgoName] = &ast.StructType{
|
||||
Struct: pos,
|
||||
Fields: fieldList,
|
||||
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
|
||||
typeExpr: &ast.StructType{
|
||||
Struct: pos,
|
||||
Fields: fieldList,
|
||||
},
|
||||
pos: pos,
|
||||
}
|
||||
case C.CXCursor_UnionDecl:
|
||||
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...)
|
||||
}
|
||||
info.elaboratedTypes[cgoName] = &ast.StructType{
|
||||
Struct: pos,
|
||||
Fields: fieldList,
|
||||
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
|
||||
typeExpr: &ast.StructType{
|
||||
Struct: pos,
|
||||
Fields: fieldList,
|
||||
},
|
||||
pos: pos,
|
||||
}
|
||||
default:
|
||||
panic("unreachable")
|
||||
@@ -506,23 +594,23 @@ func (info *fileInfo) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
|
||||
|
||||
//export tinygo_clang_struct_visitor
|
||||
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
|
||||
info *fileInfo
|
||||
pkg *cgoPackage
|
||||
})
|
||||
fieldList := passed.fieldList
|
||||
info := passed.info
|
||||
p := passed.pkg
|
||||
if C.tinygo_clang_getCursorKind(c) != C.CXCursor_FieldDecl {
|
||||
panic("expected field inside cursor")
|
||||
}
|
||||
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
||||
typ := C.tinygo_clang_getCursorType(c)
|
||||
field := &ast.Field{
|
||||
Type: info.makeASTType(typ, info.getCursorPosition(c)),
|
||||
Type: p.makeASTType(typ, p.getCursorPosition(c)),
|
||||
}
|
||||
field.Names = []*ast.Ident{
|
||||
&ast.Ident{
|
||||
NamePos: info.getCursorPosition(c),
|
||||
NamePos: p.getCursorPosition(c),
|
||||
Name: name,
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
@@ -1,6 +1,6 @@
|
||||
// +build !byollvm
|
||||
|
||||
package loader
|
||||
package cgo
|
||||
|
||||
/*
|
||||
#cgo linux CFLAGS: -I/usr/lib/llvm-8/include
|
||||
@@ -49,6 +49,10 @@ CXSourceLocation tinygo_clang_getCursorLocation(CXCursor c) {
|
||||
return clang_getCursorLocation(c);
|
||||
}
|
||||
|
||||
CXSourceRange tinygo_clang_getCursorExtent(CXCursor c) {
|
||||
return clang_getCursorExtent(c);
|
||||
}
|
||||
|
||||
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(CXCursor c) {
|
||||
return clang_Cursor_getTranslationUnit(c);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package loader
|
||||
package cgo
|
||||
|
||||
import (
|
||||
"sync"
|
||||
@@ -8,17 +8,17 @@ import (
|
||||
// #include <stdlib.h>
|
||||
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
|
||||
// pointer but only a C pointer.
|
||||
type RefMap struct {
|
||||
type refMap struct {
|
||||
refs map[unsafe.Pointer]interface{}
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
// Put stores a value in the map. It can later be retrieved using Get. It must
|
||||
// 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()
|
||||
defer m.lock.Unlock()
|
||||
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
|
||||
// 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()
|
||||
defer m.lock.Unlock()
|
||||
return m.refs[ref]
|
||||
}
|
||||
|
||||
// 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()
|
||||
defer m.lock.Unlock()
|
||||
delete(m.refs, ref)
|
||||
@@ -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.
|
||||
func (c *Compiler) expandFormalParam(v llvm.Value) []llvm.Value {
|
||||
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
|
||||
// equivalent of flattenAggregateType
|
||||
func (c *Compiler) flattenAggregate(v llvm.Value) []llvm.Value {
|
||||
|
||||
+221
-45
@@ -52,7 +52,6 @@ type Compiler struct {
|
||||
dibuilder *llvm.DIBuilder
|
||||
cu llvm.Metadata
|
||||
difiles map[string]llvm.Metadata
|
||||
ditypes map[string]llvm.Metadata
|
||||
machine llvm.TargetMachine
|
||||
targetData llvm.TargetData
|
||||
intType llvm.Type
|
||||
@@ -96,7 +95,6 @@ func NewCompiler(pkgName string, config Config) (*Compiler, error) {
|
||||
c := &Compiler{
|
||||
Config: config,
|
||||
difiles: make(map[string]llvm.Metadata),
|
||||
ditypes: make(map[string]llvm.Metadata),
|
||||
}
|
||||
|
||||
target, err := llvm.GetTargetFromTriple(config.Triple)
|
||||
@@ -153,11 +151,15 @@ func (c *Compiler) TargetData() llvm.TargetData {
|
||||
func (c *Compiler) selectGC() string {
|
||||
gc := c.GC
|
||||
if gc == "" {
|
||||
gc = "dumb"
|
||||
gc = "leaking"
|
||||
}
|
||||
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
|
||||
// fails (in any stage).
|
||||
func (c *Compiler) Compile(mainPath string) []error {
|
||||
@@ -197,7 +199,7 @@ func (c *Compiler) Compile(mainPath string) []error {
|
||||
},
|
||||
ShouldOverlay: func(path string) bool {
|
||||
switch path {
|
||||
case "machine", "os", "reflect", "runtime", "sync":
|
||||
case "machine", "os", "reflect", "runtime", "runtime/volatile", "sync":
|
||||
return true
|
||||
default:
|
||||
if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") {
|
||||
@@ -252,7 +254,7 @@ func (c *Compiler) Compile(mainPath string) []error {
|
||||
// Initialize debug information.
|
||||
if c.Debug {
|
||||
c.cu = c.dibuilder.CreateCompileUnit(llvm.DICompileUnit{
|
||||
Language: llvm.DW_LANG_Go,
|
||||
Language: 0xb, // DW_LANG_C99 (0xc, off-by-one?)
|
||||
File: mainPath,
|
||||
Dir: "",
|
||||
Producer: "TinyGo",
|
||||
@@ -387,6 +389,13 @@ func (c *Compiler) Compile(mainPath string) []error {
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -550,39 +559,166 @@ func isPointer(typ types.Type) bool {
|
||||
|
||||
// Get the DWARF type for this Go type.
|
||||
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)
|
||||
sizeInBytes := c.targetData.TypeAllocSize(llvmType)
|
||||
llvmType := c.getLLVMType(typ)
|
||||
sizeInBytes := c.targetData.TypeAllocSize(llvmType)
|
||||
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:
|
||||
var encoding llvm.DwarfTypeEncoding
|
||||
switch typ := typ.(type) {
|
||||
case *types.Basic:
|
||||
if typ.Info()&types.IsBoolean != 0 {
|
||||
encoding = llvm.DW_ATE_boolean
|
||||
} else if typ.Info()&types.IsFloat != 0 {
|
||||
encoding = llvm.DW_ATE_float
|
||||
} else if typ.Info()&types.IsComplex != 0 {
|
||||
encoding = llvm.DW_ATE_complex_float
|
||||
} else if typ.Info()&types.IsUnsigned != 0 {
|
||||
encoding = llvm.DW_ATE_unsigned
|
||||
} else if typ.Info()&types.IsInteger != 0 {
|
||||
encoding = llvm.DW_ATE_signed
|
||||
} else if typ.Kind() == types.UnsafePointer {
|
||||
encoding = llvm.DW_ATE_address
|
||||
}
|
||||
case *types.Pointer:
|
||||
encoding = llvm.DW_ATE_address
|
||||
if typ.Info()&types.IsBoolean != 0 {
|
||||
encoding = llvm.DW_ATE_boolean
|
||||
} else if typ.Info()&types.IsFloat != 0 {
|
||||
encoding = llvm.DW_ATE_float
|
||||
} else if typ.Info()&types.IsComplex != 0 {
|
||||
encoding = llvm.DW_ATE_complex_float
|
||||
} else if typ.Info()&types.IsUnsigned != 0 {
|
||||
encoding = llvm.DW_ATE_unsigned
|
||||
} else if typ.Info()&types.IsInteger != 0 {
|
||||
encoding = llvm.DW_ATE_signed
|
||||
} else if typ.Kind() == types.UnsafePointer {
|
||||
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")
|
||||
}
|
||||
// TODO: other types
|
||||
dityp = c.dibuilder.CreateBasicType(llvm.DIBasicType{
|
||||
Name: name,
|
||||
return c.dibuilder.CreateBasicType(llvm.DIBasicType{
|
||||
Name: typ.String(),
|
||||
SizeInBits: sizeInBytes * 8,
|
||||
Encoding: encoding,
|
||||
})
|
||||
c.ditypes[name] = dityp
|
||||
return dityp
|
||||
case *types.Chan:
|
||||
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
|
||||
}
|
||||
|
||||
// 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.
|
||||
if c.Debug {
|
||||
if frame.fn.Synthetic == "package initializer" {
|
||||
@@ -733,15 +877,30 @@ func (c *Compiler) parseFunc(frame *Frame) {
|
||||
// Add debug information to this parameter (if available)
|
||||
if c.Debug && frame.fn.Syntax() != nil {
|
||||
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(),
|
||||
File: c.difiles[pos.Filename],
|
||||
Line: pos.Line,
|
||||
Type: c.getDIType(param.Type()),
|
||||
Type: diType,
|
||||
AlwaysPreserve: true,
|
||||
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:
|
||||
llvmAddr := c.getValue(frame, instr.Addr)
|
||||
llvmVal := c.getValue(frame, instr.Val)
|
||||
c.emitNilCheck(frame, llvmAddr, "store")
|
||||
if c.targetData.TypeAllocSize(llvmVal.Type()) == 0 {
|
||||
// nothing to store
|
||||
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.
|
||||
if fn := instr.StaticCallee(); fn != nil {
|
||||
switch fn.RelString(nil) {
|
||||
case "device/arm.ReadRegister":
|
||||
name := fn.RelString(nil)
|
||||
switch {
|
||||
case name == "device/arm.ReadRegister":
|
||||
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)
|
||||
case "device/arm.AsmFull", "device/avr.AsmFull":
|
||||
case name == "device/arm.AsmFull" || name == "device/avr.AsmFull":
|
||||
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)
|
||||
case "syscall.Syscall", "syscall.Syscall6", "syscall.Syscall9":
|
||||
case strings.HasPrefix(name, "syscall.Syscall"):
|
||||
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)
|
||||
@@ -1416,7 +1581,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
return c.parseMakeClosure(frame, expr)
|
||||
case *ssa.MakeInterface:
|
||||
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:
|
||||
mapType := expr.Type().Underlying().(*types.Map)
|
||||
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)
|
||||
llvmKeySize := llvm.ConstInt(c.ctx.Int8Type(), keySize, 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
|
||||
case *ssa.MakeSlice:
|
||||
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:
|
||||
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
|
||||
// (which is a nil pointer). This means they can be trivially compared
|
||||
// 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 {
|
||||
case token.EQL: // ==
|
||||
return c.builder.CreateICmp(llvm.IntEQ, x, y, ""), nil
|
||||
|
||||
@@ -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
@@ -22,13 +22,10 @@ import (
|
||||
// value field.
|
||||
//
|
||||
// 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})
|
||||
itfTypeCodeGlobal := c.getTypeCode(typ)
|
||||
itfMethodSetGlobal, err := c.getTypeMethodSet(typ)
|
||||
if err != nil {
|
||||
return llvm.Value{}, nil
|
||||
}
|
||||
itfMethodSetGlobal := c.getTypeMethodSet(typ)
|
||||
itfConcreteTypeGlobal := c.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name())
|
||||
if itfConcreteTypeGlobal.IsNil() {
|
||||
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 = c.builder.CreateInsertValue(itf, itfTypeCode, 0, "")
|
||||
itf = c.builder.CreateInsertValue(itf, itfValue, 1, "")
|
||||
return itf, nil
|
||||
return itf
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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")
|
||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||
if !global.IsNil() {
|
||||
// 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)
|
||||
if ms.Len() == 0 {
|
||||
// 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())
|
||||
@@ -179,10 +176,7 @@ func (c *Compiler) getTypeMethodSet(typ types.Type) (llvm.Value, error) {
|
||||
// compiler error, so panic
|
||||
panic("cannot find function: " + f.LinkName())
|
||||
}
|
||||
fn, err := c.getInterfaceInvokeWrapper(f)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
fn := c.getInterfaceInvokeWrapper(f)
|
||||
methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{
|
||||
signatureGlobal,
|
||||
llvm.ConstPtrToInt(fn, c.uintptrType),
|
||||
@@ -195,7 +189,7 @@ func (c *Compiler) getTypeMethodSet(typ types.Type) (llvm.Value, error) {
|
||||
global.SetInitializer(value)
|
||||
global.SetGlobalConstant(true)
|
||||
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
|
||||
@@ -365,12 +359,12 @@ type interfaceInvokeWrapper struct {
|
||||
// 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
|
||||
// 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"
|
||||
wrapper := c.mod.NamedFunction(wrapperName)
|
||||
if !wrapper.IsNil() {
|
||||
// Wrapper already created. Return it directly.
|
||||
return wrapper, nil
|
||||
return wrapper
|
||||
}
|
||||
|
||||
// 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
|
||||
// with a receiver pointer bitcasted to *i8 (as done in calls on an
|
||||
// interface) is hopefully a safe (defined) operation.
|
||||
return f.LLVMFn, nil
|
||||
return f.LLVMFn
|
||||
}
|
||||
|
||||
// create wrapper function
|
||||
@@ -396,7 +390,7 @@ func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) (llvm.Value, error)
|
||||
wrapper: wrapper,
|
||||
receiverType: receiverType,
|
||||
})
|
||||
return wrapper, nil
|
||||
return wrapper
|
||||
}
|
||||
|
||||
// createInterfaceInvokeWrapper finishes the work of getInterfaceInvokeWrapper,
|
||||
|
||||
@@ -22,6 +22,36 @@ func getUses(value llvm.Value) []llvm.Value {
|
||||
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
|
||||
// first if it doesn't exist yet.
|
||||
func (c *Compiler) getLifetimeEndFunc() llvm.Value {
|
||||
|
||||
+26
-12
@@ -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) {
|
||||
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
|
||||
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
||||
// 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, "")
|
||||
} else if hashmapIsBinaryKey(keyType) {
|
||||
// key can be compared with runtime.memequal
|
||||
keyAlloca := c.builder.CreateAlloca(key.Type(), "hashmap.key")
|
||||
c.builder.CreateStore(key, keyAlloca)
|
||||
keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr")
|
||||
params := []llvm.Value{m, keyPtr, mapValuePtr}
|
||||
// Store the key in an alloca, in the entry block to avoid dynamic stack
|
||||
// growth.
|
||||
mapKeyAlloca, mapKeyPtr, mapKeySize := c.createEntryBlockAlloca(key.Type(), "hashmap.key")
|
||||
c.builder.CreateStore(key, mapKeyAlloca)
|
||||
// Fetch the value from the hashmap.
|
||||
params := []llvm.Value{m, mapKeyPtr, mapValuePtr}
|
||||
commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "")
|
||||
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{mapKeySize, mapKeyPtr}, "")
|
||||
} 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())
|
||||
}
|
||||
|
||||
// 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, "")
|
||||
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{mapValueSize, mapValuePtr}, "")
|
||||
|
||||
if commaOk {
|
||||
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{llvmValueType, c.ctx.Int1Type()}, false))
|
||||
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) {
|
||||
valueAlloca := c.builder.CreateAlloca(value.Type(), "hashmap.value")
|
||||
valueAlloca, valuePtr, valueSize := c.createEntryBlockAlloca(value.Type(), "hashmap.value")
|
||||
c.builder.CreateStore(value, valueAlloca)
|
||||
valuePtr := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "hashmap.valueptr")
|
||||
keyType = keyType.Underlying()
|
||||
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
||||
// 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, "")
|
||||
} else if hashmapIsBinaryKey(keyType) {
|
||||
// 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)
|
||||
keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr")
|
||||
params := []llvm.Value{m, keyPtr, valuePtr}
|
||||
c.createRuntimeCall("hashmapBinarySet", params, "")
|
||||
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{keySize, keyPtr}, "")
|
||||
} else {
|
||||
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 {
|
||||
@@ -68,11 +82,11 @@ func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos toke
|
||||
c.createRuntimeCall("hashmapStringDelete", params, "")
|
||||
return nil
|
||||
} 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)
|
||||
keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr")
|
||||
params := []llvm.Value{m, keyPtr}
|
||||
c.createRuntimeCall("hashmapBinaryDelete", params, "")
|
||||
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{keySize, keyPtr}, "")
|
||||
return nil
|
||||
} else {
|
||||
return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||
|
||||
@@ -37,6 +37,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||
goPasses := llvm.NewPassManager()
|
||||
defer goPasses.Dispose()
|
||||
goPasses.AddGlobalOptimizerPass()
|
||||
goPasses.AddGlobalDCEPass()
|
||||
goPasses.AddConstantPropagationPass()
|
||||
goPasses.AddAggressiveDCEPass()
|
||||
goPasses.AddFunctionAttrsPass()
|
||||
@@ -114,6 +115,13 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||
builder.Populate(modPasses)
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -25,7 +25,7 @@ func (c *Compiler) emitPointerPack(values []llvm.Value) llvm.Value {
|
||||
return llvm.ConstPointerNull(c.i8ptrType)
|
||||
} else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind {
|
||||
return c.builder.CreateBitCast(values[0], c.i8ptrType, "pack.ptr")
|
||||
} else if size <= c.targetData.TypeAllocSize(c.i8ptrType) {
|
||||
} else if size <= c.targetData.TypeAllocSize(c.i8ptrType) && !c.gcIsPrecise() {
|
||||
// Packed data fits in a pointer, so store it directly inside the
|
||||
// pointer.
|
||||
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, "")
|
||||
} else {
|
||||
// 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 {
|
||||
// A single pointer is always stored directly.
|
||||
return []llvm.Value{c.builder.CreateBitCast(ptr, valueTypes[0], "unpack.ptr")}
|
||||
} else if size <= c.targetData.TypeAllocSize(c.i8ptrType) {
|
||||
} else if size <= c.targetData.TypeAllocSize(c.i8ptrType) && !c.gcIsPrecise() {
|
||||
// Packed data stored directly in pointer.
|
||||
if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.IntegerTypeKind {
|
||||
// Keep this cast in SSA form.
|
||||
|
||||
@@ -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)}
|
||||
case callee.Name() == "runtime.nanotime":
|
||||
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":
|
||||
// This are all print instructions, which necessarily have side
|
||||
// effects but no results.
|
||||
|
||||
@@ -18,7 +18,6 @@ type Eval struct {
|
||||
TargetData llvm.TargetData
|
||||
Debug bool
|
||||
builder llvm.Builder
|
||||
dibuilder *llvm.DIBuilder
|
||||
dirtyGlobals map[llvm.Value]struct{}
|
||||
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{}{},
|
||||
}
|
||||
e.builder = mod.Context().NewBuilder()
|
||||
e.dibuilder = llvm.NewDIBuilder(mod)
|
||||
|
||||
initAll := mod.NamedFunction(name)
|
||||
bb := initAll.EntryBasicBlock()
|
||||
@@ -49,7 +47,6 @@ func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error {
|
||||
e.builder.SetInsertPointBefore(bb.FirstInstruction())
|
||||
dummy := e.builder.CreateAlloca(e.Mod.Context().Int8Type(), "dummy")
|
||||
e.builder.SetInsertPointBefore(dummy)
|
||||
e.builder.SetInstDebugLocation(bb.FirstInstruction())
|
||||
var initCalls []llvm.Value
|
||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||
if inst == dummy {
|
||||
|
||||
@@ -35,6 +35,8 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
||||
return &sideEffectResult{severity: sideEffectLimited}
|
||||
case "runtime.interfaceImplements":
|
||||
return &sideEffectResult{severity: sideEffectNone}
|
||||
case "llvm.dbg.value":
|
||||
return &sideEffectResult{severity: sideEffectNone}
|
||||
}
|
||||
if e.sideEffectFuncs == nil {
|
||||
e.sideEffectFuncs = make(map[llvm.Value]*sideEffectResult)
|
||||
|
||||
@@ -33,11 +33,12 @@ type Program struct {
|
||||
type Function struct {
|
||||
*ssa.Function
|
||||
LLVMFn llvm.Value
|
||||
linkName string // go:linkname, go:export, go:interrupt
|
||||
exported bool // go:export
|
||||
nobounds bool // go:nobounds
|
||||
flag bool // used by dead code elimination
|
||||
interrupt bool // go:interrupt
|
||||
linkName string // go:linkname, go:export, go:interrupt
|
||||
exported bool // go:export
|
||||
nobounds bool // go:nobounds
|
||||
flag bool // used by dead code elimination
|
||||
interrupt bool // go:interrupt
|
||||
inline InlineType // go:inline
|
||||
}
|
||||
|
||||
// Global variable, possibly constant.
|
||||
@@ -69,7 +70,22 @@ type Interface struct {
|
||||
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 {
|
||||
comments := map[string]*ast.CommentGroup{}
|
||||
for _, pkgInfo := range lprogram.Sorted() {
|
||||
@@ -279,6 +295,8 @@ func (f *Function) parsePragmas() {
|
||||
}
|
||||
f.linkName = parts[1]
|
||||
f.exported = true
|
||||
case "//go:inline":
|
||||
f.inline = InlineHint
|
||||
case "//go:interrupt":
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
@@ -332,6 +350,11 @@ func (f *Function) IsInterrupt() bool {
|
||||
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.
|
||||
func (f *Function) LinkName() string {
|
||||
if f.linkName != "" {
|
||||
|
||||
+20
-20
@@ -10,6 +10,8 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/tinygo-org/tinygo/cgo"
|
||||
)
|
||||
|
||||
// Program holds all packages and some metadata about the program as a whole.
|
||||
@@ -30,11 +32,10 @@ type Program struct {
|
||||
type Package struct {
|
||||
*Program
|
||||
*build.Package
|
||||
Imports map[string]*Package
|
||||
Importing bool
|
||||
Files []*ast.File
|
||||
tokenFiles map[string]*token.File
|
||||
Pkg *types.Package
|
||||
Imports map[string]*Package
|
||||
Importing bool
|
||||
Files []*ast.File
|
||||
Pkg *types.Package
|
||||
types.Info
|
||||
}
|
||||
|
||||
@@ -107,7 +108,6 @@ func (p *Program) newPackage(pkg *build.Package) *Package {
|
||||
Scopes: make(map[ast.Node]*types.Scope),
|
||||
Selections: make(map[*ast.SelectorExpr]*types.Selection),
|
||||
},
|
||||
tokenFiles: map[string]*token.File{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,16 +295,6 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
|
||||
}
|
||||
files = append(files, f)
|
||||
}
|
||||
clangIncludes := ""
|
||||
if len(p.CgoFiles) != 0 {
|
||||
if _, err := os.Stat(filepath.Join(p.TINYGOROOT, "llvm", "tools", "clang", "lib", "Headers")); !os.IsNotExist(err) {
|
||||
// Running from the source directory.
|
||||
clangIncludes = filepath.Join(p.TINYGOROOT, "llvm", "tools", "clang", "lib", "Headers")
|
||||
} else {
|
||||
// Running from the installation directory.
|
||||
clangIncludes = filepath.Join(p.TINYGOROOT, "lib", "clang", "include")
|
||||
}
|
||||
}
|
||||
for _, file := range p.CgoFiles {
|
||||
path := filepath.Join(p.Package.Dir, file)
|
||||
f, err := p.parseFile(path, parser.ParseComments)
|
||||
@@ -312,12 +302,22 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
|
||||
fileErrs = append(fileErrs, err)
|
||||
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 {
|
||||
fileErrs = append(fileErrs, errs...)
|
||||
continue
|
||||
}
|
||||
files = append(files, f)
|
||||
files = append(files, generated)
|
||||
}
|
||||
if len(fileErrs) != 0 {
|
||||
return nil, Errors{p, fileErrs}
|
||||
@@ -346,7 +346,7 @@ func (p *Package) importRecursively() error {
|
||||
p.Importing = true
|
||||
for _, to := range p.Package.Imports {
|
||||
if to == "C" {
|
||||
// Do Cgo processing in a later stage.
|
||||
// Do CGo processing in a later stage.
|
||||
continue
|
||||
}
|
||||
if _, ok := p.Imports[to]; ok {
|
||||
|
||||
@@ -537,7 +537,7 @@ func handleCompilerError(err error) {
|
||||
func main() {
|
||||
outpath := flag.String("o", "", "output filename")
|
||||
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
|
||||
gc := flag.String("gc", "", "garbage collector to use (none, dumb, marksweep)")
|
||||
gc := flag.String("gc", "", "garbage collector to use (none, leaking, conservative)")
|
||||
panicStrategy := flag.String("panic", "print", "panic strategy (abort, trap)")
|
||||
printIR := flag.Bool("printir", false, "print LLVM IR")
|
||||
dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA")
|
||||
|
||||
@@ -58,6 +58,9 @@ func TestCompiler(t *testing.T) {
|
||||
|
||||
t.Log("running tests for emulated cortex-m3...")
|
||||
for _, path := range matches {
|
||||
if path == "testdata/reflect.go" {
|
||||
continue
|
||||
}
|
||||
t.Run(path, func(t *testing.T) {
|
||||
runTest(path, tmpdir, "qemu", t)
|
||||
})
|
||||
|
||||
@@ -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], "...")
|
||||
}
|
||||
}
|
||||
@@ -99,3 +99,15 @@ const (
|
||||
var (
|
||||
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}
|
||||
)
|
||||
|
||||
@@ -73,3 +73,15 @@ const (
|
||||
var (
|
||||
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}
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -10,15 +10,15 @@ import (
|
||||
func (p GPIO) Configure(config GPIOConfig) {
|
||||
if config.Mode == GPIO_OUTPUT { // set output bit
|
||||
if p.Pin < 8 {
|
||||
*avr.DDRD |= 1 << p.Pin
|
||||
avr.DDRD.SetBits(1 << p.Pin)
|
||||
} else {
|
||||
*avr.DDRB |= 1 << (p.Pin - 8)
|
||||
avr.DDRB.SetBits(1 << (p.Pin - 8))
|
||||
}
|
||||
} else { // configure input: clear output bit
|
||||
if p.Pin < 8 {
|
||||
*avr.DDRD &^= 1 << p.Pin
|
||||
avr.DDRD.ClearBits(1 << p.Pin)
|
||||
} 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.
|
||||
func (p GPIO) Get() bool {
|
||||
if p.Pin < 8 {
|
||||
val := *avr.PIND & (1 << p.Pin)
|
||||
val := avr.PIND.Get() & (1 << p.Pin)
|
||||
return (val > 0)
|
||||
} else {
|
||||
val := *avr.PINB & (1 << (p.Pin - 8))
|
||||
val := avr.PINB.Get() & (1 << (p.Pin - 8))
|
||||
return (val > 0)
|
||||
}
|
||||
}
|
||||
|
||||
func (p GPIO) getPortMask() (*avr.RegValue, uint8) {
|
||||
func (p GPIO) getPortMask() (*avr.Register8, uint8) {
|
||||
if p.Pin < 8 {
|
||||
return avr.PORTD, 1 << p.Pin
|
||||
} else {
|
||||
@@ -45,64 +45,64 @@ func (p GPIO) getPortMask() (*avr.RegValue, uint8) {
|
||||
// InitPWM initializes the registers needed for PWM.
|
||||
func InitPWM() {
|
||||
// use waveform generation
|
||||
*avr.TCCR0A |= avr.TCCR0A_WGM00
|
||||
avr.TCCR0A.SetBits(avr.TCCR0A_WGM00)
|
||||
|
||||
// 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
|
||||
*avr.TCCR1B |= avr.TCCR1B_CS11
|
||||
avr.TCCR1B.SetBits(avr.TCCR1B_CS11)
|
||||
|
||||
// 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
|
||||
*avr.TCCR2B |= avr.TCCR2B_CS22
|
||||
avr.TCCR2B.SetBits(avr.TCCR2B_CS22)
|
||||
|
||||
// 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.
|
||||
func (pwm PWM) Configure() {
|
||||
if pwm.Pin < 8 {
|
||||
*avr.DDRD |= 1 << pwm.Pin
|
||||
avr.DDRD.SetBits(1 << pwm.Pin)
|
||||
} 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
|
||||
// 8-bit value ranging from 0 to 255.
|
||||
func (pwm PWM) Set(value uint16) {
|
||||
value8 := value >> 8
|
||||
value8 := uint8(value >> 8)
|
||||
switch pwm.Pin {
|
||||
case 3:
|
||||
// connect pwm to pin on timer 2, channel B
|
||||
*avr.TCCR2A |= avr.TCCR2A_COM2B1
|
||||
*avr.OCR2B = avr.RegValue(value8) // set pwm duty
|
||||
avr.TCCR2A.SetBits(avr.TCCR2A_COM2B1)
|
||||
avr.OCR2B.Set(value8) // set pwm duty
|
||||
case 5:
|
||||
// connect pwm to pin on timer 0, channel B
|
||||
*avr.TCCR0A |= avr.TCCR0A_COM0B1
|
||||
*avr.OCR0B = avr.RegValue(value8) // set pwm duty
|
||||
avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1)
|
||||
avr.OCR0B.Set(value8) // set pwm duty
|
||||
case 6:
|
||||
// connect pwm to pin on timer 0, channel A
|
||||
*avr.TCCR0A |= avr.TCCR0A_COM0A1
|
||||
*avr.OCR0A = avr.RegValue(value8) // set pwm duty
|
||||
avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1)
|
||||
avr.OCR0A.Set(value8) // set pwm duty
|
||||
case 9:
|
||||
// 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
|
||||
*avr.OCR1AL = avr.RegValue(value8) // set pwm duty
|
||||
avr.OCR1AL.Set(value8) // set pwm duty
|
||||
case 10:
|
||||
// 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
|
||||
*avr.OCR1BL = avr.RegValue(value8) // set pwm duty
|
||||
avr.OCR1BL.Set(value8) // set pwm duty
|
||||
case 11:
|
||||
// connect pwm to pin on timer 2, channel A
|
||||
*avr.TCCR2A |= avr.TCCR2A_COM2A1
|
||||
*avr.OCR2A = avr.RegValue(value8) // set pwm duty
|
||||
avr.TCCR2A.SetBits(avr.TCCR2A_COM2A1)
|
||||
avr.OCR2A.Set(value8) // set pwm duty
|
||||
default:
|
||||
panic("Invalid PWM pin")
|
||||
}
|
||||
@@ -121,19 +121,19 @@ func (i2c I2C) Configure(config I2CConfig) {
|
||||
}
|
||||
|
||||
// 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.
|
||||
*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:
|
||||
// SCL Frequency = CPU Clock Frequency / (16 + (2 * TWBR))
|
||||
// NOTE: TWBR should be 10 or higher for master mode.
|
||||
// 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.
|
||||
*avr.TWCR = avr.TWCR_TWEN
|
||||
avr.TWCR.Set(avr.TWCR_TWEN)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (i2c I2C) start(address uint8, write bool) {
|
||||
// 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.
|
||||
for (*avr.TWCR & avr.TWCR_TWINT) == 0 {
|
||||
for (avr.TWCR.Get() & avr.TWCR_TWINT) == 0 {
|
||||
}
|
||||
|
||||
// Write 7-bit shifted peripheral address.
|
||||
@@ -179,36 +179,36 @@ func (i2c I2C) start(address uint8, write bool) {
|
||||
// stop ends an I2C communication session.
|
||||
func (i2c I2C) stop() {
|
||||
// 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.
|
||||
for (*avr.TWCR & avr.TWCR_TWSTO) == 0 {
|
||||
for (avr.TWCR.Get() & avr.TWCR_TWSTO) == 0 {
|
||||
}
|
||||
}
|
||||
|
||||
// writeByte writes a single byte to the I2C bus.
|
||||
func (i2c I2C) writeByte(data byte) {
|
||||
// Write data to register.
|
||||
*avr.TWDR = avr.RegValue(data)
|
||||
avr.TWDR.Set(data)
|
||||
|
||||
// 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.
|
||||
for (*avr.TWCR & avr.TWCR_TWINT) == 0 {
|
||||
for (avr.TWCR.Get() & avr.TWCR_TWINT) == 0 {
|
||||
}
|
||||
}
|
||||
|
||||
// readByte reads a single byte from the I2C bus.
|
||||
func (i2c I2C) readByte() byte {
|
||||
// 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.
|
||||
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.
|
||||
@@ -226,32 +226,32 @@ func (uart UART) Configure(config UARTConfig) {
|
||||
// https://www.microchip.com/webdoc/AVRLibcReferenceManual/FAQ_1faq_wrong_baud_rate.html
|
||||
// ((F_CPU + UART_BAUD_RATE * 8L) / (UART_BAUD_RATE * 16L) - 1)
|
||||
ps := ((CPU_FREQUENCY+config.BaudRate*8)/(config.BaudRate*16) - 1)
|
||||
*avr.UBRR0H = avr.RegValue(ps >> 8)
|
||||
*avr.UBRR0L = avr.RegValue(ps & 0xff)
|
||||
avr.UBRR0H.Set(uint8(ps >> 8))
|
||||
avr.UBRR0L.Set(uint8(ps & 0xff))
|
||||
|
||||
// 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
|
||||
*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.
|
||||
func (uart UART) WriteByte(c byte) error {
|
||||
// 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
|
||||
}
|
||||
|
||||
//go:interrupt USART_RX_vect
|
||||
func handleUSART_RX() {
|
||||
// Read register to clear it.
|
||||
data := *avr.UDR0
|
||||
data := avr.UDR0.Get()
|
||||
|
||||
// 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.
|
||||
UART0.Receive(byte(data))
|
||||
}
|
||||
|
||||
@@ -808,6 +808,235 @@ func (i2c I2C) readByte() byte {
|
||||
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
|
||||
type SPI struct {
|
||||
Bus *sam.SERCOM_SPI_Type
|
||||
|
||||
@@ -9,19 +9,19 @@ import (
|
||||
// Configure sets the pin to input or output.
|
||||
func (p GPIO) Configure(config GPIOConfig) {
|
||||
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
|
||||
*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
|
||||
}
|
||||
|
||||
// Get returns the current value of a GPIO pin.
|
||||
func (p GPIO) Get() bool {
|
||||
val := *avr.PINB & (1 << p.Pin)
|
||||
val := avr.PINB.Get() & (1 << p.Pin)
|
||||
return (val > 0)
|
||||
}
|
||||
|
||||
|
||||
+12
-14
@@ -17,10 +17,10 @@ const (
|
||||
func (p GPIO) Set(value bool) {
|
||||
if value { // set bits
|
||||
port, mask := p.PortMaskSet()
|
||||
*port = mask
|
||||
port.Set(mask)
|
||||
} else { // clear bits
|
||||
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
|
||||
// returned mask is only valid as long as no other pin in the same port has been
|
||||
// changed.
|
||||
func (p GPIO) PortMaskSet() (*avr.RegValue, avr.RegValue) {
|
||||
func (p GPIO) PortMaskSet() (*avr.Register8, uint8) {
|
||||
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
|
||||
@@ -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
|
||||
// returned mask is only valid as long as no other pin in the same port has been
|
||||
// changed.
|
||||
func (p GPIO) PortMaskClear() (*avr.RegValue, avr.RegValue) {
|
||||
func (p GPIO) PortMaskClear() (*avr.Register8, uint8) {
|
||||
port, mask := p.getPortMask()
|
||||
return port, *port &^ avr.RegValue(mask)
|
||||
return port, port.Get() &^ mask
|
||||
}
|
||||
|
||||
// InitADC initializes the registers needed for ADC.
|
||||
func InitADC() {
|
||||
// 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
|
||||
*avr.ADCSRA |= avr.ADCSRA_ADEN
|
||||
avr.ADCSRA.SetBits(avr.ADCSRA_ADEN)
|
||||
}
|
||||
|
||||
// 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
|
||||
// bits. This has the same effect as shifting the return value left by 6
|
||||
// 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
|
||||
*avr.ADCSRA |= avr.ADCSRA_ADSC
|
||||
avr.ADCSRA.SetBits(avr.ADCSRA_ADSC)
|
||||
|
||||
// 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)
|
||||
high := uint16(*avr.ADCH)
|
||||
return uint16(low) | uint16(high<<8)
|
||||
return uint16(avr.ADCL.Get()) | uint16(avr.ADCH.Get())<<8
|
||||
}
|
||||
|
||||
// I2C on AVR.
|
||||
|
||||
@@ -12,77 +12,29 @@ const (
|
||||
)
|
||||
|
||||
// Fake LED numbers, for testing.
|
||||
var (
|
||||
LED uint8 = LED1
|
||||
LED1 uint8 = 0
|
||||
LED2 uint8 = 0
|
||||
LED3 uint8 = 0
|
||||
LED4 uint8 = 0
|
||||
const (
|
||||
LED = LED1
|
||||
LED1 = 0
|
||||
LED2 = 0
|
||||
LED3 = 0
|
||||
LED4 = 0
|
||||
)
|
||||
|
||||
// Fake button numbers, for testing.
|
||||
var (
|
||||
BUTTON uint8 = BUTTON1
|
||||
BUTTON1 uint8 = 5
|
||||
BUTTON2 uint8 = 6
|
||||
BUTTON3 uint8 = 7
|
||||
BUTTON4 uint8 = 8
|
||||
)
|
||||
|
||||
// Fake SPI interfaces, for testing.
|
||||
var (
|
||||
SPI0 = SPI{0}
|
||||
)
|
||||
|
||||
var (
|
||||
GPIOConfigure func(pin uint8, config GPIOConfig)
|
||||
GPIOSet func(pin uint8, value bool)
|
||||
GPIOGet func(pin uint8) bool
|
||||
SPIConfigure func(bus uint8, sck uint8, mosi uint8, miso uint8)
|
||||
SPITransfer func(bus uint8, w uint8) uint8
|
||||
const (
|
||||
BUTTON = BUTTON1
|
||||
BUTTON1 = 0
|
||||
BUTTON2 = 0
|
||||
BUTTON3 = 0
|
||||
BUTTON4 = 0
|
||||
)
|
||||
|
||||
func (p GPIO) Configure(config GPIOConfig) {
|
||||
if GPIOConfigure != nil {
|
||||
GPIOConfigure(p.Pin, config)
|
||||
}
|
||||
}
|
||||
|
||||
func (p GPIO) Set(value bool) {
|
||||
if GPIOSet != nil {
|
||||
GPIOSet(p.Pin, value)
|
||||
}
|
||||
}
|
||||
|
||||
func (p GPIO) Get() bool {
|
||||
if GPIOGet != nil {
|
||||
return GPIOGet(p.Pin)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type SPI struct {
|
||||
Bus uint8
|
||||
}
|
||||
|
||||
type SPIConfig struct {
|
||||
Frequency uint32
|
||||
SCK uint8
|
||||
MOSI uint8
|
||||
MISO uint8
|
||||
Mode uint8
|
||||
}
|
||||
|
||||
func (spi SPI) Configure(config SPIConfig) {
|
||||
if SPIConfigure != nil {
|
||||
SPIConfigure(spi.Bus, config.SCK, config.MOSI, config.MISO)
|
||||
}
|
||||
}
|
||||
|
||||
// Transfer writes/reads a single byte using the SPI interface.
|
||||
func (spi SPI) Transfer(w byte) (byte, error) {
|
||||
if SPITransfer != nil {
|
||||
return SPITransfer(spi.Bus, w), nil
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
@@ -185,21 +185,20 @@ func (i2c I2C) Tx(addr uint16, w, r []byte) error {
|
||||
}
|
||||
}
|
||||
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
|
||||
for i := range r { // read each char
|
||||
if i+1 == len(r) {
|
||||
// The 'stop' signal must be sent before reading back the last
|
||||
// byte, so that it will be sent by the I2C peripheral right
|
||||
// after the last byte has been read.
|
||||
r[i] = i2c.readLastByte()
|
||||
} else {
|
||||
r[i] = i2c.readByte()
|
||||
// To trigger stop task when last byte is received, set before resume task.
|
||||
i2c.Bus.SHORTS = nrf.TWI_SHORTS_BB_STOP
|
||||
}
|
||||
i2c.Bus.TASKS_RESUME = 1 // re-start transmission for reading
|
||||
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
|
||||
}
|
||||
|
||||
@@ -229,16 +228,6 @@ func (i2c I2C) readByte() byte {
|
||||
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.
|
||||
type SPI struct {
|
||||
Bus *nrf.SPI_Type
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// +build js,wasm nrf stm32f103xx atsamd21g18a
|
||||
// +build nrf stm32f103xx atsamd21g18a
|
||||
|
||||
package machine
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// +build gc.marksweep
|
||||
|
||||
package runtime
|
||||
|
||||
// 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
|
||||
// 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 {
|
||||
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.
|
||||
}
|
||||
|
||||
// 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.
|
||||
func sweep() {
|
||||
freeCurrentObject := false
|
||||
@@ -347,10 +265,8 @@ func sweep() {
|
||||
}
|
||||
}
|
||||
|
||||
// looksLikePointer returns whether this could be a pointer. Currently, it
|
||||
// simply returns whether it lies anywhere in the heap. Go allows interior
|
||||
// pointers so we can't check alignment or anything like that.
|
||||
func looksLikePointer(ptr uintptr) bool {
|
||||
// addressOnHeap returns whether this address points into the heap.
|
||||
func addressOnHeap(ptr uintptr) bool {
|
||||
return ptr >= poolStart && ptr < heapEnd
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -30,18 +30,6 @@ func alloc(size uintptr) unsafe.Pointer {
|
||||
return unsafe.Pointer(addr)
|
||||
}
|
||||
|
||||
func free(ptr unsafe.Pointer) {
|
||||
// Memory is never freed.
|
||||
}
|
||||
|
||||
func GC() {
|
||||
// No-op.
|
||||
}
|
||||
|
||||
func KeepAlive(x interface{}) {
|
||||
// Unimplemented. Only required with SetFinalizer().
|
||||
}
|
||||
|
||||
func SetFinalizer(obj interface{}, finalizer interface{}) {
|
||||
// Unimplemented.
|
||||
}
|
||||
@@ -12,18 +12,6 @@ import (
|
||||
|
||||
func alloc(size uintptr) unsafe.Pointer
|
||||
|
||||
func free(ptr unsafe.Pointer) {
|
||||
// Nothing to free when nothing gets allocated.
|
||||
}
|
||||
|
||||
func GC() {
|
||||
// Unimplemented.
|
||||
}
|
||||
|
||||
func KeepAlive(x interface{}) {
|
||||
// Unimplemented. Only required with SetFinalizer().
|
||||
}
|
||||
|
||||
func SetFinalizer(obj interface{}, finalizer interface{}) {
|
||||
// Unimplemented.
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
+49
-15
@@ -60,14 +60,20 @@ func hashmapTopHash(hash uint32) uint8 {
|
||||
}
|
||||
|
||||
// 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
|
||||
bucket := alloc(bucketBufSize)
|
||||
buckets := alloc(bucketBufSize * (1 << bucketBits))
|
||||
return &hashmap{
|
||||
buckets: bucket,
|
||||
buckets: buckets,
|
||||
keySize: keySize,
|
||||
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.
|
||||
//go:nobounds
|
||||
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
|
||||
bucketNumber := (uintptr(hash) & (numBuckets - 1))
|
||||
bucketSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8
|
||||
bucketAddr := uintptr(m.buckets) + bucketSize*bucketNumber
|
||||
bucket := (*hashmapBucket)(unsafe.Pointer(bucketAddr))
|
||||
|
||||
tophash := hashmapTopHash(hash)
|
||||
var lastBucket *hashmapBucket
|
||||
|
||||
// See whether the key already exists somewhere.
|
||||
var emptySlotKey unsafe.Pointer
|
||||
@@ -98,9 +111,9 @@ func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint3
|
||||
for bucket != nil {
|
||||
for i := uintptr(0); i < 8; 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)
|
||||
slotValue := unsafe.Pointer(bucketAddr + slotValueOffset)
|
||||
slotValue := unsafe.Pointer(uintptr(unsafe.Pointer(bucket)) + slotValueOffset)
|
||||
if bucket.tophash[i] == 0 && emptySlotKey == nil {
|
||||
// Found an empty slot, store it for if we couldn't find an
|
||||
// existing slot.
|
||||
@@ -109,7 +122,7 @@ func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint3
|
||||
emptySlotTophash = &bucket.tophash[i]
|
||||
}
|
||||
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)) {
|
||||
// found same key, replace it
|
||||
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
|
||||
}
|
||||
if emptySlotKey != nil {
|
||||
m.count++
|
||||
memcpy(emptySlotKey, key, uintptr(m.keySize))
|
||||
memcpy(emptySlotValue, value, uintptr(m.valueSize))
|
||||
*emptySlotTophash = tophash
|
||||
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
|
||||
}
|
||||
panic("todo: hashmap: grow bucket")
|
||||
m.count++
|
||||
memcpy(emptySlotKey, key, uintptr(m.keySize))
|
||||
memcpy(emptySlotValue, value, uintptr(m.valueSize))
|
||||
*emptySlotTophash = tophash
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
@@ -17,19 +17,19 @@ func sleepWDT(period uint8) {
|
||||
avr.Asm("cli")
|
||||
avr.Asm("wdr")
|
||||
// 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
|
||||
*avr.WDTCSR = avr.WDTCSR_WDIE | avr.RegValue(period)
|
||||
avr.WDTCSR.SetBits(avr.WDTCSR_WDIE | period)
|
||||
avr.Asm("sei")
|
||||
|
||||
// Set sleep mode to idle and enable sleep mode.
|
||||
// Note: when using something other than idle, the UART won't work
|
||||
// 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
|
||||
avr.Asm("sleep")
|
||||
|
||||
// disable sleep
|
||||
*avr.SMCR = 0
|
||||
avr.SMCR.Set(0)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -3,7 +3,7 @@
|
||||
"goos": "linux",
|
||||
"goarch": "arm",
|
||||
"compiler": "clang",
|
||||
"gc": "marksweep",
|
||||
"gc": "precise",
|
||||
"linker": "ld.lld",
|
||||
"rtlib": "compiler-rt",
|
||||
"cflags": [
|
||||
|
||||
Vendored
+22
@@ -4,6 +4,22 @@ type Thing struct {
|
||||
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 {
|
||||
return t.name
|
||||
}
|
||||
@@ -36,6 +52,12 @@ func main() {
|
||||
runFunc(func(i int) {
|
||||
println("inside fp closure:", thing.String(), i)
|
||||
}, 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) {
|
||||
|
||||
Vendored
+2
@@ -7,3 +7,5 @@ Thing.Print: foo arg: bar
|
||||
bound method: foo
|
||||
thing inside closure: foo
|
||||
inside fp closure: foo 3
|
||||
Thing.Print: arg: functional args 1
|
||||
Thing.Print: named thing arg: functional args 2
|
||||
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
package main
|
||||
|
||||
// Make sure CGo supports multiple files.
|
||||
|
||||
// int fortytwo(void);
|
||||
import "C"
|
||||
Vendored
+6
@@ -7,6 +7,8 @@ int mul(int, int);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import "C"
|
||||
|
||||
import "unsafe"
|
||||
|
||||
func main() {
|
||||
@@ -18,6 +20,10 @@ func main() {
|
||||
var y C.longlong = -(1 << 40)
|
||||
println("longlong:", y)
|
||||
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 n C.int = 15
|
||||
ptr = C.intPointer(&n)
|
||||
|
||||
Vendored
+7
@@ -10,6 +10,13 @@ int doCallback(int a, int b, binop_t cb);
|
||||
typedef int * intPointer;
|
||||
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
|
||||
void unusedFunction2(int x, __builtin_va_list args);
|
||||
|
||||
|
||||
Vendored
+4
@@ -4,6 +4,10 @@ myint: 3 5
|
||||
myint size: 2
|
||||
longlong: -1099511627776
|
||||
global: 3
|
||||
defined ints: 5 5
|
||||
defined floats: +5.800000e+000 +5.800000e+000
|
||||
defined string: defined string
|
||||
defined char: 99
|
||||
15: 15
|
||||
25: 25
|
||||
callback 1: 50
|
||||
|
||||
Vendored
+1
-1
@@ -4,7 +4,7 @@ import "time"
|
||||
|
||||
func main() {
|
||||
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)
|
||||
|
||||
n, ok := <-ch
|
||||
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
len, cap of channel: 0 0
|
||||
len, cap of channel: 0 0 false
|
||||
recv from open channel: 1 true
|
||||
received num: 2
|
||||
received num: 3
|
||||
|
||||
Vendored
+32
@@ -47,6 +47,18 @@ func main() {
|
||||
println(testMapArrayKey[arrKey])
|
||||
testMapArrayKey[arrKey] = 5555
|
||||
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) {
|
||||
@@ -56,7 +68,27 @@ func readMap(m map[string]int, key string) {
|
||||
println(" ", k, "=", v)
|
||||
}
|
||||
}
|
||||
|
||||
func lookup(m map[string]int, key string) {
|
||||
value, ok := m[key]
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+2
@@ -54,3 +54,5 @@ true false 0
|
||||
42
|
||||
4321
|
||||
5555
|
||||
tested preallocated map
|
||||
tested growing of a map
|
||||
|
||||
+46
-4
@@ -151,12 +151,54 @@ def writeGo(outdir, device):
|
||||
// {description}
|
||||
package {pkgName}
|
||||
|
||||
import "unsafe"
|
||||
import (
|
||||
"runtime/volatile"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Special type that causes loads/stores to be volatile (necessary for
|
||||
// memory-mapped registers).
|
||||
//go:volatile
|
||||
type RegValue uint8
|
||||
type Register8 struct {{
|
||||
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.
|
||||
const (
|
||||
@@ -179,7 +221,7 @@ const (
|
||||
out.write('\n\t// {description}\n'.format(**peripheral))
|
||||
for register in peripheral['registers']:
|
||||
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')
|
||||
|
||||
for peripheral in device.peripherals:
|
||||
|
||||
Reference in New Issue
Block a user