all: add bare-bones Cgo support
Этот коммит содержится в:
родитель
b99bbc880a
коммит
ecf6ffa62e
11 изменённых файлов: 408 добавлений и 9 удалений
|
@ -7,7 +7,7 @@ before_install:
|
|||
- echo "deb http://apt.llvm.org/trusty/ llvm-toolchain-trusty-7 main" | sudo tee -a /etc/apt/sources.list
|
||||
- echo "deb http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu trusty main" | sudo tee -a /etc/apt/sources.list
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install llvm-7-dev clang-7 binutils-arm-none-eabi qemu-system-arm --allow-unauthenticated -y
|
||||
- sudo apt-get install llvm-7-dev clang-7 libclang-7-dev binutils-arm-none-eabi qemu-system-arm --allow-unauthenticated -y
|
||||
- sudo ln -s /usr/bin/clang-7 /usr/local/bin/cc # work around missing -no-pie in old GCC version
|
||||
|
||||
install:
|
||||
|
|
|
@ -4,7 +4,7 @@ FROM golang:latest AS tinygo-base
|
|||
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
|
||||
echo "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-7 main" >> /etc/apt/sources.list && \
|
||||
apt-get update && \
|
||||
apt-get install -y llvm-7-dev
|
||||
apt-get install -y llvm-7-dev libclang-7-dev
|
||||
|
||||
RUN wget -O- https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||
|
||||
|
|
5
ir/ir.go
5
ir/ir.go
|
@ -362,8 +362,13 @@ func (f *Function) LinkName() string {
|
|||
func (f *Function) CName() string {
|
||||
name := f.Name()
|
||||
if strings.HasPrefix(name, "_Cfunc_") {
|
||||
// emitted by `go tool cgo`
|
||||
return name[len("_Cfunc_"):]
|
||||
}
|
||||
if strings.HasPrefix(name, "C.") {
|
||||
// created by ../loader/cgo.go
|
||||
return name[2:]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
|
|
234
loader/cgo.go
Обычный файл
234
loader/cgo.go
Обычный файл
|
@ -0,0 +1,234 @@
|
|||
package loader
|
||||
|
||||
// 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).
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// fileInfo holds all Cgo-related information of a given *ast.File.
|
||||
type fileInfo struct {
|
||||
*ast.File
|
||||
filename string
|
||||
functions []*functionInfo
|
||||
types []string
|
||||
importCPos token.Pos
|
||||
}
|
||||
|
||||
// functionInfo stores some information about a Cgo function found by libclang
|
||||
// and declared in the AST.
|
||||
type functionInfo struct {
|
||||
name string
|
||||
args []paramInfo
|
||||
result string
|
||||
}
|
||||
|
||||
// paramInfo is a parameter of a Cgo function (see functionInfo).
|
||||
type paramInfo struct {
|
||||
name string
|
||||
typeName string
|
||||
}
|
||||
|
||||
// aliasInfo encapsulates aliases between C and Go, like C.int32_t -> int32. See
|
||||
// addTypeAliases.
|
||||
type aliasInfo struct {
|
||||
typeName string
|
||||
goTypeName string
|
||||
}
|
||||
|
||||
// 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) error {
|
||||
info := &fileInfo{
|
||||
File: f,
|
||||
filename: filename,
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
// Stored for later use by generated functions, to use a somewhat sane
|
||||
// source location.
|
||||
info.importCPos = spec.Path.ValuePos
|
||||
|
||||
err = info.parseFragment(cgoComment)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove this import declaration.
|
||||
f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
|
||||
i--
|
||||
}
|
||||
|
||||
// Print the AST, for debugging.
|
||||
//ast.Print(p.fset, f)
|
||||
|
||||
// Declare functions found by libclang.
|
||||
info.addFuncDecls()
|
||||
|
||||
// Forward C types to Go types (like C.uint32_t -> uint32).
|
||||
info.addTypeAliases()
|
||||
|
||||
// Patch the AST to use the declared types and functions.
|
||||
ast.Inspect(f, info.walker)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
for _, fn := range info.functions {
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Fun,
|
||||
Name: "C." + fn.name,
|
||||
}
|
||||
args := make([]*ast.Field, len(fn.args))
|
||||
decl := &ast.FuncDecl{
|
||||
Name: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: "C." + fn.name,
|
||||
Obj: obj,
|
||||
},
|
||||
Type: &ast.FuncType{
|
||||
Func: info.importCPos,
|
||||
Params: &ast.FieldList{
|
||||
Opening: info.importCPos,
|
||||
List: args,
|
||||
Closing: info.importCPos,
|
||||
},
|
||||
Results: &ast.FieldList{
|
||||
List: []*ast.Field{
|
||||
&ast.Field{
|
||||
Type: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: "C." + fn.result,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
obj.Decl = decl
|
||||
for i, arg := range fn.args {
|
||||
args[i] = &ast.Field{
|
||||
Names: []*ast.Ident{
|
||||
&ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: arg.name,
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: "C." + arg.name,
|
||||
Decl: decl,
|
||||
},
|
||||
},
|
||||
},
|
||||
Type: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: "C." + arg.typeName,
|
||||
},
|
||||
}
|
||||
}
|
||||
info.Decls = append(info.Decls, decl)
|
||||
}
|
||||
}
|
||||
|
||||
// addTypeAliases aliases some built-in Go types with their equivalent C types.
|
||||
// It adds code like the following to the AST:
|
||||
//
|
||||
// type (
|
||||
// C.int8_t = int8
|
||||
// C.int16_t = int16
|
||||
// // ...
|
||||
// )
|
||||
func (info *fileInfo) addTypeAliases() {
|
||||
aliases := []aliasInfo{
|
||||
aliasInfo{"C.int8_t", "int8"},
|
||||
aliasInfo{"C.int16_t", "int16"},
|
||||
aliasInfo{"C.int32_t", "int32"},
|
||||
aliasInfo{"C.int64_t", "int64"},
|
||||
aliasInfo{"C.uint8_t", "uint8"},
|
||||
aliasInfo{"C.uint16_t", "uint16"},
|
||||
aliasInfo{"C.uint32_t", "uint32"},
|
||||
aliasInfo{"C.uint64_t", "uint64"},
|
||||
aliasInfo{"C.uintptr_t", "uintptr"},
|
||||
}
|
||||
gen := &ast.GenDecl{
|
||||
TokPos: info.importCPos,
|
||||
Tok: token.TYPE,
|
||||
Lparen: info.importCPos,
|
||||
Rparen: info.importCPos,
|
||||
}
|
||||
for _, alias := range aliases {
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Typ,
|
||||
Name: alias.typeName,
|
||||
}
|
||||
typeSpec := &ast.TypeSpec{
|
||||
Name: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: alias.typeName,
|
||||
Obj: obj,
|
||||
},
|
||||
Assign: info.importCPos,
|
||||
Type: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: alias.goTypeName,
|
||||
},
|
||||
}
|
||||
obj.Decl = typeSpec
|
||||
gen.Specs = append(gen.Specs, typeSpec)
|
||||
}
|
||||
info.Decls = append(info.Decls, gen)
|
||||
}
|
||||
|
||||
// walker replaces all "C".<something> call expressions to literal
|
||||
// "C.<something>" expressions. This is impossible to write in Go (a dot cannot
|
||||
// be used in the middle of a name) so is used as a new namespace for C call
|
||||
// expressions.
|
||||
func (info *fileInfo) walker(node ast.Node) bool {
|
||||
switch node := node.(type) {
|
||||
case *ast.CallExpr:
|
||||
fun, ok := node.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
x, ok := fun.X.(*ast.Ident)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if x.Name == "C" {
|
||||
node.Fun = &ast.Ident{
|
||||
NamePos: x.NamePos,
|
||||
Name: "C." + fun.Sel.Name,
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
12
loader/libclang-cfuncs.go
Обычный файл
12
loader/libclang-cfuncs.go
Обычный файл
|
@ -0,0 +1,12 @@
|
|||
package loader
|
||||
|
||||
/*
|
||||
#include <clang-c/Index.h> // if this fails, install libclang-7-dev
|
||||
|
||||
// The gateway function
|
||||
int tinygo_clang_visitor_cgo(CXCursor c, CXCursor parent, CXClientData client_data) {
|
||||
int tinygo_clang_visitor(CXCursor c, CXCursor parent, CXClientData client_data);
|
||||
return tinygo_clang_visitor(c, parent, client_data);
|
||||
}
|
||||
*/
|
||||
import "C"
|
105
loader/libclang.go
Обычный файл
105
loader/libclang.go
Обычный файл
|
@ -0,0 +1,105 @@
|
|||
package loader
|
||||
|
||||
// This file parses a fragment of C with libclang and stores the result for AST
|
||||
// modification. It does not touch the AST itself.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -I/usr/lib/llvm-7/include
|
||||
#cgo LDFLAGS: -L/usr/lib/llvm-7/lib -lclang
|
||||
#include <clang-c/Index.h> // if this fails, install libclang-7-dev
|
||||
#include <stdlib.h>
|
||||
|
||||
int tinygo_clang_visitor_cgo(CXCursor c, CXCursor parent, CXClientData client_data);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
var globalFileInfo *fileInfo
|
||||
|
||||
func (info *fileInfo) parseFragment(fragment string) error {
|
||||
index := C.clang_createIndex(0, 1)
|
||||
defer C.clang_disposeIndex(index)
|
||||
|
||||
filenameC := C.CString("cgo-fake.c")
|
||||
defer C.free(unsafe.Pointer(filenameC))
|
||||
|
||||
fragmentC := C.CString(fragment)
|
||||
defer C.free(unsafe.Pointer(fragmentC))
|
||||
|
||||
unsavedFile := C.struct_CXUnsavedFile{
|
||||
Filename: filenameC,
|
||||
Length: C.ulong(len(fragment)),
|
||||
Contents: fragmentC,
|
||||
}
|
||||
|
||||
var unit C.CXTranslationUnit
|
||||
errCode := C.clang_parseTranslationUnit2(
|
||||
index,
|
||||
filenameC,
|
||||
(**C.char)(unsafe.Pointer(uintptr(0))), 0, // command line args
|
||||
&unsavedFile, 1, // unsaved files
|
||||
C.CXTranslationUnit_None,
|
||||
&unit)
|
||||
if errCode != 0 {
|
||||
panic("loader: failed to parse source with libclang")
|
||||
}
|
||||
defer C.clang_disposeTranslationUnit(unit)
|
||||
|
||||
if C.clang_getNumDiagnostics(unit) != 0 {
|
||||
return errors.New("cgo: libclang cannot parse fragment")
|
||||
}
|
||||
|
||||
if globalFileInfo != nil {
|
||||
// There is a race condition here but that doesn't really matter as it
|
||||
// is a sanity check anyway.
|
||||
panic("libclang.go cannot be used concurrently yet")
|
||||
}
|
||||
globalFileInfo = info
|
||||
defer func() {
|
||||
globalFileInfo = nil
|
||||
}()
|
||||
|
||||
cursor := C.clang_getTranslationUnitCursor(unit)
|
||||
C.clang_visitChildren(cursor, (*[0]byte)((unsafe.Pointer(C.tinygo_clang_visitor_cgo))), C.CXClientData(uintptr(0)))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//export tinygo_clang_visitor
|
||||
func tinygo_clang_visitor(c, parent C.CXCursor, client_data C.CXClientData) C.int {
|
||||
info := globalFileInfo
|
||||
kind := C.clang_getCursorKind(c)
|
||||
switch kind {
|
||||
case C.CXCursor_FunctionDecl:
|
||||
name := getString(C.clang_getCursorSpelling(c))
|
||||
cursorType := C.clang_getCursorType(c)
|
||||
if C.clang_isFunctionTypeVariadic(cursorType) != 0 {
|
||||
return C.CXChildVisit_Continue // not supported
|
||||
}
|
||||
numArgs := C.clang_Cursor_getNumArguments(c)
|
||||
fn := &functionInfo{name: name}
|
||||
info.functions = append(info.functions, fn)
|
||||
for i := C.int(0); i < numArgs; i++ {
|
||||
arg := C.clang_Cursor_getArgument(c, C.uint(i))
|
||||
argName := getString(C.clang_getCursorSpelling(arg))
|
||||
argType := C.clang_getArgType(cursorType, C.uint(i))
|
||||
argTypeName := getString(C.clang_getTypeSpelling(argType))
|
||||
fn.args = append(fn.args, paramInfo{argName, argTypeName})
|
||||
}
|
||||
resultType := C.clang_getCursorResultType(c)
|
||||
resultTypeName := getString(C.clang_getTypeSpelling(resultType))
|
||||
fn.result = resultTypeName
|
||||
}
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
|
||||
func getString(clangString C.CXString) (s string) {
|
||||
rawString := C.clang_getCString(clangString)
|
||||
s = C.GoString(rawString)
|
||||
C.clang_disposeString(clangString)
|
||||
return
|
||||
}
|
|
@ -268,10 +268,6 @@ func (p *Package) Check() error {
|
|||
|
||||
// parseFiles parses the loaded list of files and returns this list.
|
||||
func (p *Package) parseFiles() ([]*ast.File, error) {
|
||||
if len(p.CgoFiles) != 0 {
|
||||
return nil, errors.New("loader: todo cgo: " + p.CgoFiles[0])
|
||||
}
|
||||
|
||||
// TODO: do this concurrently.
|
||||
var files []*ast.File
|
||||
var fileErrs []error
|
||||
|
@ -279,9 +275,27 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
|
|||
f, err := p.parseFile(filepath.Join(p.Package.Dir, file), parser.ParseComments)
|
||||
if err != nil {
|
||||
fileErrs = append(fileErrs, err)
|
||||
} else {
|
||||
files = append(files, f)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
fileErrs = append(fileErrs, err)
|
||||
continue
|
||||
}
|
||||
files = append(files, f)
|
||||
}
|
||||
for _, file := range p.CgoFiles {
|
||||
path := filepath.Join(p.Package.Dir, file)
|
||||
f, err := p.parseFile(path, parser.ParseComments)
|
||||
if err != nil {
|
||||
fileErrs = append(fileErrs, err)
|
||||
continue
|
||||
}
|
||||
err = p.processCgo(path, f)
|
||||
if err != nil {
|
||||
fileErrs = append(fileErrs, err)
|
||||
continue
|
||||
}
|
||||
files = append(files, f)
|
||||
}
|
||||
if len(fileErrs) != 0 {
|
||||
return nil, Errors{p, fileErrs}
|
||||
|
@ -298,7 +312,7 @@ func (p *Package) Import(to string) (*types.Package, error) {
|
|||
if _, ok := p.Imports[to]; ok {
|
||||
return p.Imports[to].Pkg, nil
|
||||
} else {
|
||||
panic("package not imported: " + to)
|
||||
return nil, errors.New("package not imported: " + to)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -309,6 +323,10 @@ func (p *Package) Import(to string) (*types.Package, error) {
|
|||
func (p *Package) importRecursively() error {
|
||||
p.Importing = true
|
||||
for _, to := range p.Package.Imports {
|
||||
if to == "C" {
|
||||
// Do Cgo processing in a later stage.
|
||||
continue
|
||||
}
|
||||
if _, ok := p.Imports[to]; ok {
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -155,6 +155,7 @@ func LoadTarget(target string) (*TargetSpec, error) {
|
|||
*spec = TargetSpec{
|
||||
Triple: target,
|
||||
BuildTags: []string{runtime.GOOS, runtime.GOARCH},
|
||||
Compiler: commands["clang"],
|
||||
Linker: "cc",
|
||||
LDFlags: []string{"-no-pie"}, // WARNING: clang < 5.0 requires -nopie
|
||||
Objcopy: "objcopy",
|
||||
|
|
9
testdata/cgo/main.c
предоставленный
Обычный файл
9
testdata/cgo/main.c
предоставленный
Обычный файл
|
@ -0,0 +1,9 @@
|
|||
#include <stdint.h>
|
||||
|
||||
int32_t fortytwo() {
|
||||
return 42;
|
||||
}
|
||||
|
||||
int32_t mul(int32_t a, int32_t b) {
|
||||
return a * b;
|
||||
}
|
13
testdata/cgo/main.go
предоставленный
Обычный файл
13
testdata/cgo/main.go
предоставленный
Обычный файл
|
@ -0,0 +1,13 @@
|
|||
package main
|
||||
|
||||
/*
|
||||
#include <stdint.h>
|
||||
int32_t fortytwo(void);
|
||||
int32_t mul(int32_t a, int32_t b);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
func main() {
|
||||
println("fortytwo:", C.fortytwo())
|
||||
println("mul:", C.mul(int32(3), 5))
|
||||
}
|
2
testdata/cgo/out.txt
предоставленный
Обычный файл
2
testdata/cgo/out.txt
предоставленный
Обычный файл
|
@ -0,0 +1,2 @@
|
|||
fortytwo: 42
|
||||
mul: 15
|
Загрузка…
Создание таблицы
Сослаться в новой задаче