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://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
|
- 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 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
|
- sudo ln -s /usr/bin/clang-7 /usr/local/bin/cc # work around missing -no-pie in old GCC version
|
||||||
|
|
||||||
install:
|
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 - && \
|
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 && \
|
echo "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-7 main" >> /etc/apt/sources.list && \
|
||||||
apt-get update && \
|
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
|
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 {
|
func (f *Function) CName() string {
|
||||||
name := f.Name()
|
name := f.Name()
|
||||||
if strings.HasPrefix(name, "_Cfunc_") {
|
if strings.HasPrefix(name, "_Cfunc_") {
|
||||||
|
// emitted by `go tool cgo`
|
||||||
return name[len("_Cfunc_"):]
|
return name[len("_Cfunc_"):]
|
||||||
}
|
}
|
||||||
|
if strings.HasPrefix(name, "C.") {
|
||||||
|
// created by ../loader/cgo.go
|
||||||
|
return name[2:]
|
||||||
|
}
|
||||||
return ""
|
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.
|
// parseFiles parses the loaded list of files and returns this list.
|
||||||
func (p *Package) parseFiles() ([]*ast.File, error) {
|
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.
|
// TODO: do this concurrently.
|
||||||
var files []*ast.File
|
var files []*ast.File
|
||||||
var fileErrs []error
|
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)
|
f, err := p.parseFile(filepath.Join(p.Package.Dir, file), parser.ParseComments)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fileErrs = append(fileErrs, err)
|
fileErrs = append(fileErrs, err)
|
||||||
} else {
|
continue
|
||||||
files = append(files, f)
|
|
||||||
}
|
}
|
||||||
|
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 {
|
if len(fileErrs) != 0 {
|
||||||
return nil, Errors{p, fileErrs}
|
return nil, Errors{p, fileErrs}
|
||||||
|
@ -298,7 +312,7 @@ func (p *Package) Import(to string) (*types.Package, error) {
|
||||||
if _, ok := p.Imports[to]; ok {
|
if _, ok := p.Imports[to]; ok {
|
||||||
return p.Imports[to].Pkg, nil
|
return p.Imports[to].Pkg, nil
|
||||||
} else {
|
} 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 {
|
func (p *Package) importRecursively() error {
|
||||||
p.Importing = true
|
p.Importing = true
|
||||||
for _, to := range p.Package.Imports {
|
for _, to := range p.Package.Imports {
|
||||||
|
if to == "C" {
|
||||||
|
// Do Cgo processing in a later stage.
|
||||||
|
continue
|
||||||
|
}
|
||||||
if _, ok := p.Imports[to]; ok {
|
if _, ok := p.Imports[to]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,6 +155,7 @@ func LoadTarget(target string) (*TargetSpec, error) {
|
||||||
*spec = TargetSpec{
|
*spec = TargetSpec{
|
||||||
Triple: target,
|
Triple: target,
|
||||||
BuildTags: []string{runtime.GOOS, runtime.GOARCH},
|
BuildTags: []string{runtime.GOOS, runtime.GOARCH},
|
||||||
|
Compiler: commands["clang"],
|
||||||
Linker: "cc",
|
Linker: "cc",
|
||||||
LDFlags: []string{"-no-pie"}, // WARNING: clang < 5.0 requires -nopie
|
LDFlags: []string{"-no-pie"}, // WARNING: clang < 5.0 requires -nopie
|
||||||
Objcopy: "objcopy",
|
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
|
Загрузка…
Создание таблицы
Сослаться в новой задаче