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. import ( "bytes" "crypto/sha256" "crypto/sha512" "encoding/hex" "fmt" "go/ast" "go/scanner" "go/token" "path/filepath" "strconv" "strings" "unsafe" "tinygo.org/x/go-llvm" ) /* #include // If this fails, libclang headers aren't available. Please take a look here: https://tinygo.org/docs/guides/build/ #include #include #include // This struct should be ABI-compatible on all platforms (uintptr_t has the same // alignment etc. as void*) but does not include void* pointers that are not // always real pointers. // The Go garbage collector assumes that all non-nil pointer-typed integers are // actually pointers. This is not always true, as data[1] often contains 0x1, // which is clearly not a valid pointer. Usually the GC won't catch this issue, // but occasionally it will leading to a crash with a vague error message. typedef struct { enum CXCursorKind kind; int xdata; uintptr_t data[3]; } GoCXCursor; // Forwarding functions. They are implemented in libclang_stubs.c and forward to // the real functions without doing anything else, thus they are entirely // compatible with the versions without tinygo_ prefix. The only difference is // the CXCursor type, which has been replaced with GoCXCursor. GoCXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu); unsigned tinygo_clang_visitChildren(GoCXCursor parent, CXCursorVisitor visitor, CXClientData client_data); CXString tinygo_clang_getCursorSpelling(GoCXCursor c); CXString tinygo_clang_getCursorPrettyPrinted(GoCXCursor c, CXPrintingPolicy Policy); CXPrintingPolicy tinygo_clang_getCursorPrintingPolicy(GoCXCursor c); enum CXCursorKind tinygo_clang_getCursorKind(GoCXCursor c); CXType tinygo_clang_getCursorType(GoCXCursor c); GoCXCursor tinygo_clang_getTypeDeclaration(CXType t); CXType tinygo_clang_getTypedefDeclUnderlyingType(GoCXCursor c); CXType tinygo_clang_getCursorResultType(GoCXCursor c); int tinygo_clang_Cursor_getNumArguments(GoCXCursor c); GoCXCursor tinygo_clang_Cursor_getArgument(GoCXCursor c, unsigned i); enum CX_StorageClass tinygo_clang_Cursor_getStorageClass(GoCXCursor c); CXSourceLocation tinygo_clang_getCursorLocation(GoCXCursor c); CXSourceRange tinygo_clang_getCursorExtent(GoCXCursor c); CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(GoCXCursor c); long long tinygo_clang_getEnumConstantDeclValue(GoCXCursor c); CXType tinygo_clang_getEnumDeclIntegerType(GoCXCursor c); unsigned tinygo_clang_Cursor_isAnonymous(GoCXCursor c); unsigned tinygo_clang_Cursor_isBitField(GoCXCursor c); unsigned tinygo_clang_Cursor_isMacroFunctionLike(GoCXCursor c); int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); int tinygo_clang_enum_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); void tinygo_clang_inclusion_visitor(CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, CXClientData client_data); */ import "C" // storedRefs stores references to types, used for clang_visitChildren. var storedRefs refMap var diagnosticSeverity = [...]string{ C.CXDiagnostic_Ignored: "ignored", C.CXDiagnostic_Note: "note", C.CXDiagnostic_Warning: "warning", C.CXDiagnostic_Error: "error", C.CXDiagnostic_Fatal: "fatal", } // Alias so that cgo.go (which doesn't import Clang related stuff and is in // theory decoupled from Clang) can also use this type. type clangCursor = C.GoCXCursor func init() { // Check that we haven't messed up LLVM versioning. // This can happen when llvm_config_*.go files in either this or the // tinygo.org/x/go-llvm packages is incorrect. It should not ever happen // with byollvm. if C.LLVM_VERSION_STRING != llvm.Version { panic("incorrect build: using LLVM version " + llvm.Version + " in the tinygo.org/x/llvm package, and version " + C.LLVM_VERSION_STRING + " in the ./cgo package") } } func (f *cgoFile) readNames(fragment string, cflags []string, filename string, callback func(map[string]clangCursor)) { index := C.clang_createIndex(0, 0) defer C.clang_disposeIndex(index) // pretend to be a .c file filenameC := C.CString(filename + "!cgo.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, } // convert Go slice of strings to C array of strings. cmdargsC := C.malloc(C.size_t(len(cflags)) * C.size_t(unsafe.Sizeof(uintptr(0)))) defer C.free(cmdargsC) cmdargs := (*[1 << 16]*C.char)(cmdargsC) for i, cflag := range cflags { s := C.CString(cflag) cmdargs[i] = s defer C.free(unsafe.Pointer(s)) } var unit C.CXTranslationUnit errCode := C.clang_parseTranslationUnit2( index, filenameC, (**C.char)(cmdargsC), C.int(len(cflags)), // command line args &unsavedFile, 1, // unsaved files C.CXTranslationUnit_DetailedPreprocessingRecord, &unit) if errCode != 0 { // This is probably a bug in the usage of libclang. panic("cgo: failed to parse source with libclang") } defer C.clang_disposeTranslationUnit(unit) // Report parser and type errors. if numDiagnostics := int(C.clang_getNumDiagnostics(unit)); numDiagnostics != 0 { addDiagnostic := func(diagnostic C.CXDiagnostic) { spelling := getString(C.clang_getDiagnosticSpelling(diagnostic)) severity := diagnosticSeverity[C.clang_getDiagnosticSeverity(diagnostic)] location := C.clang_getDiagnosticLocation(diagnostic) pos := f.getClangLocationPosition(location, unit) f.addError(pos, severity+": "+spelling) } for i := 0; i < numDiagnostics; i++ { diagnostic := C.clang_getDiagnostic(unit, C.uint(i)) addDiagnostic(diagnostic) // Child diagnostics (like notes on redefinitions). diagnostics := C.clang_getChildDiagnostics(diagnostic) for j := 0; j < int(C.clang_getNumDiagnosticsInSet(diagnostics)); j++ { addDiagnostic(C.clang_getDiagnosticInSet(diagnostics, C.uint(j))) } } } // Extract information required by CGo. ref := storedRefs.Put(f) 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)) // Determine files read during CGo processing, for caching. inclusionCallback := func(includedFile C.CXFile) { // Get full file path. path := getString(C.clang_getFileName(includedFile)) // Get contents of file (that should be in-memory). size := C.size_t(0) rawData := C.clang_getFileContents(unit, includedFile, &size) if rawData == nil { // Sanity check. This should (hopefully) never trigger. panic("libclang: file contents was not loaded") } data := (*[1 << 24]byte)(unsafe.Pointer(rawData))[:size] // Hash the contents if it isn't hashed yet. if _, ok := f.visitedFiles[path]; !ok { // already stored sum := sha512.Sum512_224(data) f.visitedFiles[path] = sum[:] } } inclusionCallbackRef := storedRefs.Put(inclusionCallback) defer storedRefs.Remove(inclusionCallbackRef) C.clang_getInclusions(unit, C.CXInclusionVisitor(C.tinygo_clang_inclusion_visitor), C.CXClientData(inclusionCallbackRef)) // Do all the C AST operations inside a callback. This makes sure that // libclang related memory is only freed after it is not necessary anymore. callback(f.names) } // Convert the AST node under the given Clang cursor to a Go AST node and return // it. func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) { kind := C.tinygo_clang_getCursorKind(c) pos := f.getCursorPosition(c) switch kind { case C.CXCursor_FunctionDecl: cursorType := C.tinygo_clang_getCursorType(c) numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c)) obj := &ast.Object{ Kind: ast.Fun, Name: "C." + name, } exportName := name localName := name var stringSignature string if C.tinygo_clang_Cursor_getStorageClass(c) == C.CX_SC_Static { // A static function is assigned a globally unique symbol name based // on the file path (like _Cgo_static_2d09198adbf58f4f4655_foo) and // has a different Go name in the form of C.foo!symbols.go instead // of just C.foo. path := f.importPath + "/" + filepath.Base(f.fset.File(f.file.Pos()).Name()) staticIDBuf := sha256.Sum256([]byte(path)) staticID := hex.EncodeToString(staticIDBuf[:10]) exportName = "_Cgo_static_" + staticID + "_" + name localName = name + "!" + filepath.Base(path) // Create a signature. This is necessary for MacOS to forward the // call, because MacOS doesn't support aliases like ELF and PE do. // (There is N_INDR but __attribute__((alias("..."))) doesn't work). policy := C.tinygo_clang_getCursorPrintingPolicy(c) defer C.clang_PrintingPolicy_dispose(policy) C.clang_PrintingPolicy_setProperty(policy, C.CXPrintingPolicy_TerseOutput, 1) stringSignature = getString(C.tinygo_clang_getCursorPrettyPrinted(c, policy)) stringSignature = strings.Replace(stringSignature, " "+name+"(", " "+exportName+"(", 1) stringSignature = strings.TrimPrefix(stringSignature, "static ") } args := make([]*ast.Field, numArgs) decl := &ast.FuncDecl{ Doc: &ast.CommentGroup{ List: []*ast.Comment{ { Slash: pos - 1, Text: "//export " + exportName, }, }, }, Name: &ast.Ident{ NamePos: pos, Name: "C." + localName, Obj: obj, }, Type: &ast.FuncType{ Func: pos, Params: &ast.FieldList{ Opening: pos, List: args, Closing: pos, }, }, } var doc []string if C.clang_isFunctionTypeVariadic(cursorType) != 0 { doc = append(doc, "//go:variadic") } if _, ok := f.noescapingFuncs[name]; ok { doc = append(doc, "//go:noescape") f.noescapingFuncs[name].used = true } if len(doc) != 0 { decl.Doc.List = append(decl.Doc.List, &ast.Comment{ Slash: pos - 1, Text: strings.Join(doc, "\n"), }) } for i := 0; i < numArgs; i++ { arg := C.tinygo_clang_Cursor_getArgument(c, C.uint(i)) argName := getString(C.tinygo_clang_getCursorSpelling(arg)) argType := C.clang_getArgType(cursorType, C.uint(i)) if argName == "" { argName = "$" + strconv.Itoa(i) } args[i] = &ast.Field{ Names: []*ast.Ident{ { NamePos: pos, Name: argName, Obj: &ast.Object{ Kind: ast.Var, Name: argName, Decl: decl, }, }, }, Type: f.makeDecayingASTType(argType, pos), } } resultType := C.tinygo_clang_getCursorResultType(c) if resultType.kind != C.CXType_Void { decl.Type.Results = &ast.FieldList{ List: []*ast.Field{ { Type: f.makeASTType(resultType, pos), }, }, } } obj.Decl = decl return decl, stringSignature case C.CXCursor_StructDecl, C.CXCursor_UnionDecl: typ := f.makeASTRecordType(c, pos) typeName := "C." + name typeExpr := typ.typeExpr if typ.unionSize != 0 { // Convert to a single-field struct type. typeExpr = f.makeUnionField(typ) } obj := &ast.Object{ Kind: ast.Typ, Name: typeName, } typeSpec := &ast.TypeSpec{ Name: &ast.Ident{ NamePos: typ.pos, Name: typeName, Obj: obj, }, Type: typeExpr, } obj.Decl = typeSpec return typeSpec, typ case C.CXCursor_TypedefDecl: typeName := "C." + name underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(c) obj := &ast.Object{ Kind: ast.Typ, Name: typeName, } typeSpec := &ast.TypeSpec{ Name: &ast.Ident{ NamePos: pos, Name: typeName, Obj: obj, }, Type: f.makeASTType(underlyingType, pos), } if underlyingType.kind != C.CXType_Enum { typeSpec.Assign = pos } obj.Decl = typeSpec return typeSpec, nil case C.CXCursor_VarDecl: cursorType := C.tinygo_clang_getCursorType(c) typeExpr := f.makeASTType(cursorType, pos) gen := &ast.GenDecl{ TokPos: pos, Tok: token.VAR, Lparen: token.NoPos, Rparen: token.NoPos, Doc: &ast.CommentGroup{ List: []*ast.Comment{ { Slash: pos - 1, Text: "//go:extern " + name, }, }, }, } obj := &ast.Object{ Kind: ast.Var, Name: "C." + name, } valueSpec := &ast.ValueSpec{ Names: []*ast.Ident{{ NamePos: pos, Name: "C." + name, Obj: obj, }}, Type: typeExpr, } obj.Decl = valueSpec gen.Specs = append(gen.Specs, valueSpec) return gen, nil case C.CXCursor_MacroDefinition: tokenPos, value := f.getMacro(c) expr, scannerError := parseConst(tokenPos, f.fset, value, nil, token.NoPos, f) if scannerError != nil { f.errors = append(f.errors, *scannerError) return nil, nil } gen := &ast.GenDecl{ TokPos: token.NoPos, Tok: token.CONST, Lparen: token.NoPos, Rparen: token.NoPos, } obj := &ast.Object{ Kind: ast.Con, Name: "C." + name, } valueSpec := &ast.ValueSpec{ Names: []*ast.Ident{{ NamePos: pos, Name: "C." + name, Obj: obj, }}, Values: []ast.Expr{expr}, } obj.Decl = valueSpec gen.Specs = append(gen.Specs, valueSpec) return gen, nil case C.CXCursor_EnumDecl: obj := &ast.Object{ Kind: ast.Typ, Name: "C." + name, } underlying := C.tinygo_clang_getEnumDeclIntegerType(c) // TODO: gc's CGo implementation uses types such as `uint32` for enums // instead of types such as C.int, which are used here. typeSpec := &ast.TypeSpec{ Name: &ast.Ident{ NamePos: pos, Name: "C." + name, Obj: obj, }, Assign: pos, Type: f.makeASTType(underlying, pos), } obj.Decl = typeSpec return typeSpec, nil case C.CXCursor_EnumConstantDecl: value := C.tinygo_clang_getEnumConstantDeclValue(c) expr := &ast.BasicLit{ ValuePos: pos, Kind: token.INT, Value: strconv.FormatInt(int64(value), 10), } gen := &ast.GenDecl{ TokPos: token.NoPos, Tok: token.CONST, Lparen: token.NoPos, Rparen: token.NoPos, } obj := &ast.Object{ Kind: ast.Con, Name: "C." + name, } valueSpec := &ast.ValueSpec{ Names: []*ast.Ident{{ NamePos: pos, Name: "C." + name, Obj: obj, }}, Values: []ast.Expr{expr}, } obj.Decl = valueSpec gen.Specs = append(gen.Specs, valueSpec) return gen, nil default: f.addError(pos, fmt.Sprintf("internal error: unknown cursor type: %d", kind)) return nil, nil } } // Return whether this is a macro that's also function-like, like this: // // #define add(a, b) (a+b) func (f *cgoFile) isFunctionLikeMacro(c clangCursor) bool { if C.tinygo_clang_getCursorKind(c) != C.CXCursor_MacroDefinition { return false } return C.tinygo_clang_Cursor_isMacroFunctionLike(c) != 0 } // Get the macro value: the position in the source file and the string value of // the macro. func (f *cgoFile) getMacro(c clangCursor) (pos token.Pos, value string) { // Extract tokens from the Clang tokenizer. // See: https://stackoverflow.com/a/19074846/559350 sourceRange := C.tinygo_clang_getCursorExtent(c) tu := C.tinygo_clang_Cursor_getTranslationUnit(c) var rawTokens *C.CXToken var numTokens C.unsigned C.clang_tokenize(tu, sourceRange, &rawTokens, &numTokens) tokens := unsafe.Slice(rawTokens, numTokens) defer C.clang_disposeTokens(tu, rawTokens, numTokens) // Convert this range of tokens back to source text. // Ugly, but it works well enough. sourceBuf := &bytes.Buffer{} var startOffset int for i, token := range tokens { spelling := getString(C.clang_getTokenSpelling(tu, token)) location := C.clang_getTokenLocation(tu, token) var tokenOffset C.unsigned C.clang_getExpansionLocation(location, nil, nil, nil, &tokenOffset) if i == 0 { // The first token is the macro name itself. // Skip it (after using its location). startOffset = int(tokenOffset) } else { // Later tokens are the macro contents. for int(tokenOffset) > (startOffset + sourceBuf.Len()) { // Pad the source text with whitespace (that must have been // present in the original source as well). sourceBuf.WriteByte(' ') } sourceBuf.WriteString(spelling) } } value = sourceBuf.String() // Obtain the position of this token. This is the position of the first // character in the 'value' string and is used to report errors at the // correct location in the source file. pos = f.getCursorPosition(c) return } func getString(clangString C.CXString) (s string) { rawString := C.clang_getCString(clangString) s = C.GoString(rawString) C.clang_disposeString(clangString) return } //export tinygo_clang_globals_visitor func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int { f := storedRefs.Get(unsafe.Pointer(client_data)).(*cgoFile) switch C.tinygo_clang_getCursorKind(c) { case C.CXCursor_FunctionDecl: name := getString(C.tinygo_clang_getCursorSpelling(c)) f.names[name] = c case C.CXCursor_StructDecl: name := getString(C.tinygo_clang_getCursorSpelling(c)) if name != "" { f.names["struct_"+name] = c } case C.CXCursor_UnionDecl: name := getString(C.tinygo_clang_getCursorSpelling(c)) if name != "" { f.names["union_"+name] = c } case C.CXCursor_TypedefDecl: typedefType := C.tinygo_clang_getCursorType(c) name := getString(C.clang_getTypedefName(typedefType)) f.names[name] = c case C.CXCursor_VarDecl: name := getString(C.tinygo_clang_getCursorSpelling(c)) f.names[name] = c case C.CXCursor_MacroDefinition: name := getString(C.tinygo_clang_getCursorSpelling(c)) f.names[name] = c case C.CXCursor_EnumDecl: name := getString(C.tinygo_clang_getCursorSpelling(c)) if name != "" { // Named enum, which can be referenced from Go using C.enum_foo. f.names["enum_"+name] = c } // The enum fields are in global scope, so recurse to visit them. return C.CXChildVisit_Recurse case C.CXCursor_EnumConstantDecl: // We arrive here because of the "Recurse" above. name := getString(C.tinygo_clang_getCursorSpelling(c)) f.names[name] = c } return C.CXChildVisit_Continue } // Get the precise location in the source code. Used for uniquely identifying // source locations. func (f *cgoFile) getUniqueLocationID(pos token.Pos, cursor C.GoCXCursor) interface{} { clangLocation := C.tinygo_clang_getCursorLocation(cursor) var file C.CXFile var line C.unsigned var column C.unsigned C.clang_getFileLocation(clangLocation, &file, &line, &column, nil) location := token.Position{ Filename: getString(C.clang_getFileName(file)), Line: int(line), Column: int(column), } if location.Filename == "" || location.Line == 0 { // Not sure when this would happen, but protect from it anyway. f.addError(pos, "could not find file/line information") } return location } // getCursorPosition returns a usable token.Pos from a libclang cursor. func (p *cgoPackage) getCursorPosition(cursor C.GoCXCursor) token.Pos { return p.getClangLocationPosition(C.tinygo_clang_getCursorLocation(cursor), C.tinygo_clang_Cursor_getTranslationUnit(cursor)) } // getClangLocationPosition returns a usable token.Pos based on a libclang // location and translation unit. 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 (p *cgoPackage) getClangLocationPosition(location C.CXSourceLocation, tu C.CXTranslationUnit) token.Pos { var file C.CXFile var line C.unsigned var column C.unsigned var offset C.unsigned C.clang_getExpansionLocation(location, &file, &line, &column, &offset) if line == 0 || file == nil { // Invalid token. return token.NoPos } filename := getString(C.clang_getFileName(file)) 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. var size C.size_t sourcePtr := C.clang_getFileContents(tu, file, &size) source := ((*[1 << 28]byte)(unsafe.Pointer(sourcePtr)))[:size:size] lines := []int{0} for i := 0; i < len(source)-1; i++ { if source[i] == '\n' { lines = append(lines, i+1) } } f := p.fset.AddFile(filename, -1, int(size)) f.SetLines(lines) p.tokenFiles[filename] = f // Add dummy file AST, to satisfy the type checker. astFile := &ast.File{ Package: f.Pos(0), Name: ast.NewIdent(p.packageName), } setASTFileFields(astFile, f.Pos(0), f.Pos(int(size))) p.cgoFiles = append(p.cgoFiles, astFile) } positionFile := p.tokenFiles[filename] // Check for alternative line/column information (set with a line directive). var filename2String C.CXString var line2 C.unsigned var column2 C.unsigned C.clang_getPresumedLocation(location, &filename2String, &line2, &column2) filename2 := getString(filename2String) if filename2 != filename || line2 != line || column2 != column { // The location was changed with a preprocessor directive. // TODO: this only works for locations that are added in order. Adding // line/column info to a file that already has line/column info after // the given offset is ignored. positionFile.AddLineColumnInfo(int(offset), filename2, int(line2), int(column2)) } return positionFile.Pos(int(offset)) } // addError is a utility function to add an error to the list of errors. It will // convert the token position to a line/column position first, and call // addErrorAt. func (p *cgoPackage) addError(pos token.Pos, msg string) { p.addErrorAt(p.fset.PositionFor(pos, true), msg) } // addErrorAfter is like addError, but adds the text `after` to the source // location. func (p *cgoPackage) addErrorAfter(pos token.Pos, after, msg string) { position := p.fset.PositionFor(pos, true) lines := strings.Split(after, "\n") if len(lines) != 1 { // Adjust lines. // For why we can't just do pos+token.Pos(len(after)), see: // https://github.com/golang/go/issues/35803 position.Line += len(lines) - 1 position.Column = len(lines[len(lines)-1]) + 1 } else { position.Column += len(after) } p.addErrorAt(position, msg) } // addErrorAt is a utility function to add an error to the list of errors. func (p *cgoPackage) addErrorAt(position token.Position, msg string) { p.errors = append(p.errors, scanner.Error{ Pos: position, Msg: msg, }) } // makeDecayingASTType does the same as makeASTType but takes care of decaying // types (arrays in function parameters, etc). It is otherwise identical to // makeASTType. func (f *cgoFile) makeDecayingASTType(typ C.CXType, pos token.Pos) ast.Expr { // Strip typedefs, if any. underlyingType := typ if underlyingType.kind == C.CXType_Elaborated { // Starting with LLVM 16, the elaborated type is used for more types. // According to the Clang documentation, the elaborated type has no // semantic meaning so can be stripped (it is used to better convey type // name information). // Source: // https://clang.llvm.org/doxygen/classclang_1_1ElaboratedType.html#details // > The type itself is always "sugar", used to express what was written // > in the source code but containing no additional semantic information. underlyingType = C.clang_Type_getNamedType(underlyingType) } if underlyingType.kind == C.CXType_Typedef { c := C.tinygo_clang_getTypeDeclaration(underlyingType) underlyingType = C.tinygo_clang_getTypedefDeclUnderlyingType(c) // TODO: support a chain of typedefs. At the moment, it seems to get // stuck in an endless loop when trying to get to the most underlying // type. } // Check for decaying type. An example would be an array type in a // parameter. This declaration: // void foo(char buf[6]); // is the same as this one: // void foo(char *buf); // But this one: // void bar(char buf[6][4]); // equals this: // void bar(char *buf[4]); // so not all array dimensions should be stripped, just the first one. // TODO: there are more kinds of decaying types. if underlyingType.kind == C.CXType_ConstantArray { // Apply type decaying. pointeeType := C.clang_getElementType(underlyingType) return &ast.StarExpr{ Star: pos, X: f.makeASTType(pointeeType, pos), } } return f.makeASTType(typ, pos) } // 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 (f *cgoFile) makeASTType(typ C.CXType, pos token.Pos) ast.Expr { var typeName string switch typ.kind { case C.CXType_Char_S, C.CXType_Char_U: typeName = "C.char" case C.CXType_SChar: typeName = "C.schar" case C.CXType_UChar: typeName = "C.uchar" case C.CXType_Short: typeName = "C.short" case C.CXType_UShort: typeName = "C.ushort" case C.CXType_Int: typeName = "C.int" case C.CXType_UInt: typeName = "C.uint" case C.CXType_Long: typeName = "C.long" case C.CXType_ULong: typeName = "C.ulong" case C.CXType_LongLong: typeName = "C.longlong" case C.CXType_ULongLong: typeName = "C.ulonglong" case C.CXType_Bool: typeName = "bool" case C.CXType_Float, C.CXType_Double, C.CXType_LongDouble: switch C.clang_Type_getSizeOf(typ) { case 4: typeName = "float32" case 8: typeName = "float64" default: // Don't do anything, rely on the fallback code to show a somewhat // sensible error message like "undeclared name: C.long double". } case C.CXType_Complex: switch C.clang_Type_getSizeOf(typ) { case 8: typeName = "complex64" case 16: typeName = "complex128" } case C.CXType_Pointer: pointeeType := C.clang_getPointeeType(typ) if pointeeType.kind == C.CXType_Void { // void* type is translated to Go as unsafe.Pointer return &ast.SelectorExpr{ X: &ast.Ident{ NamePos: pos, Name: "unsafe", }, Sel: &ast.Ident{ NamePos: pos, Name: "Pointer", }, } } return &ast.StarExpr{ Star: pos, X: f.makeASTType(pointeeType, pos), } case C.CXType_ConstantArray: return &ast.ArrayType{ Lbrack: pos, Len: &ast.BasicLit{ ValuePos: pos, Kind: token.INT, Value: strconv.FormatInt(int64(C.clang_getArraySize(typ)), 10), }, Elt: f.makeASTType(C.clang_getElementType(typ), pos), } case C.CXType_FunctionProto: // Be compatible with gc, which uses the *[0]byte type for function // pointer types. // Return type [0]byte because this is a function type, not a pointer to // this function type. return &ast.ArrayType{ Lbrack: pos, Len: &ast.BasicLit{ ValuePos: pos, Kind: token.INT, Value: "0", }, Elt: &ast.Ident{ NamePos: pos, Name: "byte", }, } case C.CXType_Typedef: name := getString(C.clang_getTypedefName(typ)) c := C.tinygo_clang_getTypeDeclaration(typ) return &ast.Ident{ NamePos: pos, Name: f.getASTDeclName(name, c, false), } case C.CXType_Elaborated: underlying := C.clang_Type_getNamedType(typ) switch underlying.kind { case C.CXType_Record: return f.makeASTType(underlying, pos) case C.CXType_Enum: return f.makeASTType(underlying, pos) case C.CXType_Typedef: return f.makeASTType(underlying, pos) default: typeKindSpelling := getString(C.clang_getTypeKindSpelling(underlying.kind)) f.addError(pos, fmt.Sprintf("unknown elaborated type (libclang type kind %s)", typeKindSpelling)) typeName = "" } case C.CXType_Record: cursor := C.tinygo_clang_getTypeDeclaration(typ) name := getString(C.tinygo_clang_getCursorSpelling(cursor)) var cgoRecordPrefix string switch C.tinygo_clang_getCursorKind(cursor) { case C.CXCursor_StructDecl: cgoRecordPrefix = "struct_" case C.CXCursor_UnionDecl: cgoRecordPrefix = "union_" default: // makeASTRecordType will create an appropriate error. cgoRecordPrefix = "record_" } if name == "" || C.tinygo_clang_Cursor_isAnonymous(cursor) != 0 { // Anonymous record, probably inside a typedef. location := f.getUniqueLocationID(pos, cursor) name = f.getUnnamedDeclName("_Ctype_"+cgoRecordPrefix+"__", location) } else { name = cgoRecordPrefix + name } return &ast.Ident{ NamePos: pos, Name: f.getASTDeclName(name, cursor, false), } case C.CXType_Enum: cursor := C.tinygo_clang_getTypeDeclaration(typ) name := getString(C.tinygo_clang_getCursorSpelling(cursor)) if name == "" { // Anonymous enum, probably inside a typedef. location := f.getUniqueLocationID(pos, cursor) name = f.getUnnamedDeclName("_Ctype_enum___", location) } else { name = "enum_" + name } return &ast.Ident{ NamePos: pos, Name: f.getASTDeclName(name, cursor, false), } } if typeName == "" { // Report this as an error. typeSpelling := getString(C.clang_getTypeSpelling(typ)) typeKindSpelling := getString(C.clang_getTypeKindSpelling(typ.kind)) f.addError(pos, fmt.Sprintf("unknown C type: %v (libclang type kind %s)", typeSpelling, typeKindSpelling)) typeName = "C." } return &ast.Ident{ NamePos: pos, Name: typeName, } } // getIntegerType returns an AST node that defines types such as C.int. func (p *cgoPackage) getIntegerType(name string, cursor clangCursor) *ast.TypeSpec { pos := p.getCursorPosition(cursor) // Find a Go type that matches the size and signedness of the given C type. underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(cursor) var goName string typeSize := C.clang_Type_getSizeOf(underlyingType) switch name { case "C.char": if typeSize != 1 { // This happens for some very special purpose architectures // (DSPs etc.) that are not currently targeted. // https://www.embecosm.com/2017/04/18/non-8-bit-char-support-in-clang-and-llvm/ p.addError(pos, fmt.Sprintf("unknown char width: %d", typeSize)) } switch underlyingType.kind { case C.CXType_Char_S: goName = "int8" case C.CXType_Char_U: goName = "uint8" } case "C.schar", "C.short", "C.int", "C.long", "C.longlong": switch typeSize { case 1: goName = "int8" case 2: goName = "int16" case 4: goName = "int32" case 8: goName = "int64" } case "C.uchar", "C.ushort", "C.uint", "C.ulong", "C.ulonglong": switch typeSize { case 1: goName = "uint8" case 2: goName = "uint16" case 4: goName = "uint32" case 8: goName = "uint64" } } if goName == "" { // should not happen p.addError(pos, "internal error: did not find Go type for C type "+name) goName = "int" } // Construct an *ast.TypeSpec for this type. obj := &ast.Object{ Kind: ast.Typ, Name: name, } spec := &ast.TypeSpec{ Name: &ast.Ident{ NamePos: pos, Name: name, Obj: obj, }, Type: &ast.Ident{ NamePos: pos, Name: goName, }, } obj.Decl = spec return spec } // makeASTRecordType parses a C record (struct or union) and translates it into // a Go struct type. func (f *cgoFile) makeASTRecordType(cursor C.GoCXCursor, pos token.Pos) *elaboratedTypeInfo { fieldList := &ast.FieldList{ Opening: pos, Closing: pos, } var bitfieldList []bitfieldInfo inBitfield := false bitfieldNum := 0 ref := storedRefs.Put(struct { fieldList *ast.FieldList file *cgoFile inBitfield *bool bitfieldNum *int bitfieldList *[]bitfieldInfo }{fieldList, f, &inBitfield, &bitfieldNum, &bitfieldList}) defer storedRefs.Remove(ref) C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref)) renameFieldKeywords(fieldList) switch C.tinygo_clang_getCursorKind(cursor) { case C.CXCursor_StructDecl: return &elaboratedTypeInfo{ typeExpr: &ast.StructType{ Struct: pos, Fields: fieldList, }, pos: pos, bitfields: bitfieldList, } case C.CXCursor_UnionDecl: typeInfo := &elaboratedTypeInfo{ typeExpr: &ast.StructType{ Struct: pos, Fields: fieldList, }, pos: pos, bitfields: bitfieldList, } if len(fieldList.List) <= 1 { // Useless union, treat it as a regular struct. return typeInfo } if bitfieldList != nil { // This is valid C... but please don't do this. f.addError(pos, "bitfield in a union is not supported") } typ := C.tinygo_clang_getCursorType(cursor) alignInBytes := int64(C.clang_Type_getAlignOf(typ)) sizeInBytes := int64(C.clang_Type_getSizeOf(typ)) if sizeInBytes == 0 { f.addError(pos, "zero-length union is not supported") } typeInfo.unionSize = sizeInBytes typeInfo.unionAlign = alignInBytes return typeInfo default: cursorKind := C.tinygo_clang_getCursorKind(cursor) cursorKindSpelling := getString(C.clang_getCursorKindSpelling(cursorKind)) f.addError(pos, fmt.Sprintf("expected StructDecl or UnionDecl, not %s", cursorKindSpelling)) return &elaboratedTypeInfo{ typeExpr: &ast.StructType{ Struct: pos, }, pos: pos, } } } //export tinygo_clang_struct_visitor func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int { passed := storedRefs.Get(unsafe.Pointer(client_data)).(struct { fieldList *ast.FieldList file *cgoFile inBitfield *bool bitfieldNum *int bitfieldList *[]bitfieldInfo }) fieldList := passed.fieldList f := passed.file inBitfield := passed.inBitfield bitfieldNum := passed.bitfieldNum bitfieldList := passed.bitfieldList pos := f.getCursorPosition(c) switch cursorKind := C.tinygo_clang_getCursorKind(c); cursorKind { case C.CXCursor_FieldDecl: // Expected. This is a regular field. case C.CXCursor_StructDecl, C.CXCursor_UnionDecl: // Ignore. The next field will be the struct/union itself. return C.CXChildVisit_Continue default: cursorKindSpelling := getString(C.clang_getCursorKindSpelling(cursorKind)) f.addError(pos, fmt.Sprintf("expected FieldDecl in struct or union, not %s", cursorKindSpelling)) return C.CXChildVisit_Continue } name := getString(C.tinygo_clang_getCursorSpelling(c)) if name == "" { // Assume this is a bitfield of 0 bits. // Warning: this is not necessarily true! return C.CXChildVisit_Continue } typ := C.tinygo_clang_getCursorType(c) field := &ast.Field{ Type: f.makeASTType(typ, f.getCursorPosition(c)), } offsetof := int64(C.clang_Type_getOffsetOf(C.tinygo_clang_getCursorType(parent), C.CString(name))) alignOf := int64(C.clang_Type_getAlignOf(typ) * 8) bitfieldOffset := offsetof % alignOf if bitfieldOffset != 0 { if C.tinygo_clang_Cursor_isBitField(c) != 1 { f.addError(pos, "expected a bitfield") return C.CXChildVisit_Continue } if !*inBitfield { *bitfieldNum++ } bitfieldName := "__bitfield_" + strconv.Itoa(*bitfieldNum) prevField := fieldList.List[len(fieldList.List)-1] if !*inBitfield { // The previous element also was a bitfield, but wasn't noticed // then. Add it now. *inBitfield = true *bitfieldList = append(*bitfieldList, bitfieldInfo{ field: prevField, name: prevField.Names[0].Name, startBit: 0, pos: prevField.Names[0].NamePos, }) prevField.Names[0].Name = bitfieldName prevField.Names[0].Obj.Name = bitfieldName } prevBitfield := &(*bitfieldList)[len(*bitfieldList)-1] prevBitfield.endBit = bitfieldOffset *bitfieldList = append(*bitfieldList, bitfieldInfo{ field: prevField, name: name, startBit: bitfieldOffset, pos: pos, }) return C.CXChildVisit_Continue } *inBitfield = false field.Names = []*ast.Ident{ { NamePos: pos, Name: name, Obj: &ast.Object{ Kind: ast.Var, Name: name, Decl: field, }, }, } fieldList.List = append(fieldList.List, field) return C.CXChildVisit_Continue } //export tinygo_clang_inclusion_visitor func tinygo_clang_inclusion_visitor(includedFile C.CXFile, inclusionStack *C.CXSourceLocation, includeLen C.unsigned, clientData C.CXClientData) { callback := storedRefs.Get(unsafe.Pointer(clientData)).(func(C.CXFile)) callback(includedFile) }