mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-08 04:53:42 +00:00
7939c060ce
These types are often known to the compiler already. Moving them into
the compiler is an important step for two related reasons:
* It makes the compiler better testable. Together with
https://github.com/tinygo-org/tinygo/pull/1008 it will make it
possible to test the compiler without having to load the runtime
package.
* It makes it easier to compile packages independently as the type
information of the runtime package doesn't need to be present.
I had to use hack to get this to work well: internal/task.Task is now an
opaque struct. This is necessary because there is a dependency from
*runtime.channel -> *runtime.channelBlockedList -> *internal/task.Task.
I don't want to include the definition of the internal/task.Task struct
in the compiler directly as that would make changing the internal/task
package a lot harder and the compiler doesn't need to know the layout of
that struct anyway.
345 lines
10 KiB
Go
345 lines
10 KiB
Go
// +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("<unknown %s>", 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)
|
|
}
|
|
}
|