// +build none // This file generates runtimetypes.go from the AST of the TinyGo runtime // package. This type information is necessary to avoid having to compile the // runtime to compile any package. package main import ( "bytes" "fmt" "go/ast" "go/format" "go/token" "io" "io/ioutil" "os" "strings" "golang.org/x/tools/go/packages" ) // The list of runtime types known by the compiler. var runtimeTypes = []string{ // panic, recover "_defer", // strings "_string", "stringIterator", // map "hashmap", "hashmapBucket", "hashmapIterator", // channel "channel", "channelBlockedList", "chanSelectState", // interface "_interface", "interfaceMethodInfo", "typecodeID", "structField", "typeInInterface", // func "funcValue", "funcValueWithSignature", } // The list of runtime calls known to the compiler. var runtimeCalls = []string{ // panic, recover "_panic", "_recover", "nilPanic", "lookupPanic", "slicePanic", "chanMakePanic", "negativeShiftPanic", // string "stringEqual", "stringLess", "stringConcat", "stringFromBytes", "stringToBytes", "stringFromRunes", "stringToRunes", "stringFromUnicode", "stringNext", // complex "complex64div", "complex128div", // slice "sliceAppend", "sliceCopy", // memory "alloc", "trackPointer", // print builtin "printbool", "printint8", "printuint8", "printint16", "printuint16", "printint32", "printuint32", "printint64", "printuint64", "printfloat32", "printfloat64", "printcomplex64", "printcomplex128", "printstring", "printspace", "printnl", "printptr", "printmap", "printitf", // hashmap "hashmapMake", "hashmapLen", "hashmapNext", "hashmapStringGet", "hashmapStringSet", "hashmapStringDelete", "hashmapInterfaceGet", "hashmapInterfaceSet", "hashmapInterfaceDelete", "hashmapBinaryGet", "hashmapBinarySet", "hashmapBinaryDelete", // channel, concurrency "tryChanSelect", "chanMake", "chanSend", "chanRecv", "chanClose", "chanSelect", "deadlock", // interface, reflect "interfaceEqual", "interfaceImplements", "interfaceMethod", "typeAssert", "interfaceTypeAssert", // func "getFuncPtr", } // makeDefs generates runtimetypes.go and writes it out after formatting it. func makeDefs() error { // Load the runtime package. pkgs, err := packages.Load(&packages.Config{ Mode: packages.NeedSyntax, BuildFlags: []string{"-tags=gc.extalloc"}, }, "../src/runtime") if err != nil { return err } if len(pkgs) != 1 { return fmt.Errorf("expected 1 package, got %d", len(pkgs)) } runtimePkg := pkgs[0] if len(runtimePkg.Errors) != 0 { return runtimePkg.Errors[0] } // Start creating the new Go file. buf := &bytes.Buffer{} buf.WriteString(`// Autogenerated by mkruntimetypes.go, DO NOT EDIT. package compiler // This file contains definitions for runtime types and functions, so that the // runtime package can be compiled independently of other packages. import ( "go/token" "go/types" "strconv" ) // getRuntimeType constructs a new runtime type with the given name. The types // constructed here must match the types in the runtime package. func (c *compilerContext) getRuntimeType(name string) types.Type { if c.program != nil { return c.program.ImportedPackage("runtime").Type(name).Type() } if typ, ok := c.runtimeTypes[name]; ok { // This type was already created. return typ } typeName := types.NewTypeName(token.NoPos, c.runtimePkg, name, nil) named := types.NewNamed(typeName, nil, nil) // Make sure recursive types are only defined once. c.runtimeTypes[name] = named var fieldTypes []types.Type switch name { `) err = makeTypeDefs(buf, runtimePkg) if err != nil { return err } buf.WriteString(` default: panic("could not find runtime type: runtime." + name) } // Create the named struct type. var fields []*types.Var for i, t := range fieldTypes { // Field name doesn't matter: this type is only used to create a LLVM // struct type which don't have field names. fields = append(fields, types.NewField(token.NoPos, nil, "field"+strconv.Itoa(i), t, false)) } named.SetUnderlying(types.NewStruct(fields, nil)) return named } // getRuntimeFuncType constructs a new runtime function signature with the given // name. The function signatures constructed here must match the functions in // the runtime package. func (c *compilerContext) getRuntimeFuncType(name string) *types.Signature { var params []*types.Var addParam := func(name string, typ types.Type) { params = append(params, types.NewParam(token.NoPos, c.runtimePkg, name, typ)) } var results []*types.Var addResult := func(typ types.Type) { results = append(results, types.NewParam(token.NoPos, c.runtimePkg, "", typ)) } switch name { `) err = makeFuncDefs(buf, runtimePkg) if err != nil { return err } buf.WriteString(` default: panic("unknown runtime call: runtime." + name) } return types.NewSignature(nil, types.NewTuple(params...), types.NewTuple(results...), false) } `) source, err := format.Source(buf.Bytes()) if err != nil { // Fallback (useful for investigating errors). source = buf.Bytes() } err2 := ioutil.WriteFile("runtimetypes.go", source, 0666) if err2 != nil { return err2 // error from ioutil.WriteFile } return err // error from format.Source (if any) } // makeTypeDefs generates the switch body of the getRuntimeType function. func makeTypeDefs(w io.Writer, runtimePkg *packages.Package) error { typeSpecs := map[string]*ast.TypeSpec{} for _, file := range runtimePkg.Syntax { for _, decl := range file.Decls { switch decl := decl.(type) { case *ast.GenDecl: if decl.Tok != token.TYPE { continue } for _, spec := range decl.Specs { typeSpec := spec.(*ast.TypeSpec) typeSpecs[typeSpec.Name.Name] = typeSpec } } } } for _, name := range runtimeTypes { typeSpec := typeSpecs[name] if typeSpec == nil { return fmt.Errorf("could not find type: %s", name) } fmt.Fprintf(w, "\tcase %#v:\n", typeSpec.Name.Name) if name == "channelBlockedList" { fmt.Fprintf(w, "\t\ttaskType := types.NewNamed(types.NewTypeName(token.NoPos, c.taskPkg, \"Task\", nil), nil, nil)\n") } fmt.Fprintf(w, "\t\tfieldTypes = []types.Type{\n") for _, field := range typeSpec.Type.(*ast.StructType).Fields.List { fieldType := getTypeFromExpr(field.Type, typeSpec.Name.Name) for _, ident := range field.Names { fmt.Fprintf(w, "\t\t\t%s, // %s\n", fieldType, ident.Name) } } fmt.Fprintf(w, "\t\t}\n") } return nil } // makeFuncDefs generates the switch body of the getRuntimeFuncType function. func makeFuncDefs(w io.Writer, runtimePkg *packages.Package) error { functions := map[string]*ast.FuncDecl{} for _, file := range runtimePkg.Syntax { for _, decl := range file.Decls { switch decl := decl.(type) { case *ast.FuncDecl: functions[decl.Name.Name] = decl default: } } } for _, name := range runtimeCalls { decl := functions[name] if decl == nil { return fmt.Errorf("could not find function: %s", name) } fmt.Fprintf(w, "\tcase %#v:\n", decl.Name.Name) for _, field := range decl.Type.Params.List { typeString := getTypeFromExpr(field.Type, "") for _, name := range field.Names { fmt.Fprintf(w, "\t\taddParam(%#v, %s)\n", name.Name, typeString) } } if decl.Type.Results != nil { for _, field := range decl.Type.Results.List { typeString := getTypeFromExpr(field.Type, "") for range field.Names { fmt.Fprintf(w, "\t\taddResult(%s)\n", typeString) } if len(field.Names) == 0 { fmt.Fprintf(w, "\t\taddResult(%s)\n", typeString) } } } } return nil } // getTypeFromExpr returns a string which is a piece of Go code that constructs // the type (as given in ast.Expr) using the go/types package. func getTypeFromExpr(typ ast.Expr, currentTypeName string) string { switch typ := typ.(type) { case *ast.Ident: if typ.Name == currentTypeName { // Assume a global named "named" which refers to the currently // created named type. return "named" } switch typ.Name { case "bool", "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "uintptr", "float32", "float64", "complex64", "complex128", "string", "byte", "rune": // Built-in types. return fmt.Sprintf("types.Typ[types.%s]", strings.Title(typ.Name)) case "chanState": // runtime.chanState is a named type, but that doesn't matter when // generating LLVM IR. return fmt.Sprintf("types.Typ[types.Uint8]") default: // Assume that we can simply get the type recursively. return fmt.Sprintf("c.getRuntimeType(%#v)", typ.Name) } case *ast.StarExpr: return fmt.Sprintf("types.NewPointer(%s)", getTypeFromExpr(typ.X, currentTypeName)) case *ast.InterfaceType: if len(typ.Methods.List) != 0 { // Unimplemented: interfaces with methods. return "interface{?}" } return "types.NewInterfaceType(nil, nil)" case *ast.ArrayType: // Slices and arrays. Assume the array length is a numeric constant and // not a Go named constant for example. elementType := getTypeFromExpr(typ.Elt, currentTypeName) if typ.Len == nil { return fmt.Sprintf("types.NewSlice(%s)", elementType) } length := typ.Len.(*ast.BasicLit).Value return fmt.Sprintf("types.NewArray(%s, %s)", elementType, length) case *ast.SelectorExpr: s := typ.X.(*ast.Ident).Name + "." + typ.Sel.Name switch s { case "unsafe.Pointer": return "types.Typ[types.UnsafePointer]" case "task.Task": // Assume there is a variable taskType which refers to the task.Task // structure. return "taskType" default: return fmt.Sprintf("", s) } case *ast.StructType: // Inline struct type. var fields string for _, field := range typ.Fields.List { fieldType := getTypeFromExpr(field.Type, currentTypeName) for _, ident := range field.Names { fields += fmt.Sprintf("\t\t\ttypes.NewField(token.NoPos, nil, %#v, %s, false),\n", ident.Name, fieldType) } } return fmt.Sprintf("types.NewStruct([]*types.Var{\n%s\t\t}, nil)", fields) default: // Dump the raw typ value, for debugging. return fmt.Sprintf("%#v", typ) } } func main() { err := makeDefs() if err != nil { fmt.Fprintln(os.Stderr, "could not create defs:", err) os.Exit(1) } }