compiler: fix crash with linked lists in interfaces

This commit fixes the following issue:
https://github.com/tinygo-org/tinygo/issues/309
Also, it prepares for some other reflect-related changes that should
make it easier to add support for named types (etc.) in the future.
Этот коммит содержится в:
Ayke van Laethem 2019-08-02 16:58:42 +02:00 коммит произвёл Ron Evans
родитель a04db67ea9
коммит 33dc4b5121
6 изменённых файлов: 93 добавлений и 58 удалений

Просмотреть файл

@ -48,7 +48,7 @@ func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signa
// Closure is: {context, function pointer} // Closure is: {context, function pointer}
funcValueScalar = funcPtr funcValueScalar = funcPtr
case funcValueSwitch: case funcValueSwitch:
sigGlobal := c.getFuncSignature(sig) sigGlobal := c.getTypeCode(sig)
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature" funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName) funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
if funcValueWithSignatureGlobal.IsNil() { if funcValueWithSignatureGlobal.IsNil() {
@ -73,22 +73,6 @@ func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signa
return funcValue return funcValue
} }
// getFuncSignature returns a global for identification of a particular function
// signature. It is used in runtime.funcValueWithSignature and in calls to
// getFuncPtr.
func (c *Compiler) getFuncSignature(sig *types.Signature) llvm.Value {
typeCodeName := getTypeCodeName(sig)
sigGlobalName := "reflect/types.type:" + typeCodeName
sigGlobal := c.mod.NamedGlobal(sigGlobalName)
if sigGlobal.IsNil() {
sigGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), sigGlobalName)
sigGlobal.SetInitializer(llvm.Undef(c.ctx.Int8Type()))
sigGlobal.SetGlobalConstant(true)
sigGlobal.SetLinkage(llvm.InternalLinkage)
}
return sigGlobal
}
// extractFuncScalar returns some scalar that can be used in comparisons. It is // extractFuncScalar returns some scalar that can be used in comparisons. It is
// a cheap operation. // a cheap operation.
func (c *Compiler) extractFuncScalar(funcValue llvm.Value) llvm.Value { func (c *Compiler) extractFuncScalar(funcValue llvm.Value) llvm.Value {
@ -110,7 +94,7 @@ func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (
funcPtr = c.builder.CreateExtractValue(funcValue, 1, "") funcPtr = c.builder.CreateExtractValue(funcValue, 1, "")
case funcValueSwitch: case funcValueSwitch:
llvmSig := c.getRawFuncType(sig) llvmSig := c.getRawFuncType(sig)
sigGlobal := c.getFuncSignature(sig) sigGlobal := c.getTypeCode(sig)
funcPtr = c.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "") funcPtr = c.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
funcPtr = c.builder.CreateIntToPtr(funcPtr, llvmSig, "") funcPtr = c.builder.CreateIntToPtr(funcPtr, llvmSig, "")
default: default:

Просмотреть файл

@ -45,10 +45,32 @@ func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.
// It returns a pointer to an external global which should be replaced with the // It returns a pointer to an external global which should be replaced with the
// real type in the interface lowering pass. // real type in the interface lowering pass.
func (c *Compiler) getTypeCode(typ types.Type) llvm.Value { func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
globalName := "type:" + getTypeCodeName(typ) globalName := "reflect/types.type:" + getTypeCodeName(typ)
global := c.mod.NamedGlobal(globalName) global := c.mod.NamedGlobal(globalName)
if global.IsNil() { if global.IsNil() {
// Create a new typecode global.
global = llvm.AddGlobal(c.mod, c.getLLVMRuntimeType("typecodeID"), globalName) global = llvm.AddGlobal(c.mod, c.getLLVMRuntimeType("typecodeID"), globalName)
// Some type classes contain more information for underlying types or
// element types. Store it directly in the typecode global to make
// reflect lowering simpler.
var elementType types.Type
switch typ := typ.(type) {
case *types.Named:
elementType = typ.Underlying()
case *types.Chan:
elementType = typ.Elem()
case *types.Pointer:
elementType = typ.Elem()
case *types.Slice:
elementType = typ.Elem()
}
if elementType != nil {
// Set the 'references' field of the runtime.typecodeID struct.
globalValue := c.getZeroValue(global.Type().ElementType())
globalValue = llvm.ConstInsertValue(globalValue, c.getTypeCode(elementType), []uint32{0})
global.SetInitializer(globalValue)
global.SetLinkage(llvm.PrivateLinkage)
}
global.SetGlobalConstant(true) global.SetGlobalConstant(true)
} }
return global return global
@ -58,14 +80,11 @@ func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
// interface lowering pass to assign type codes as expected by the reflect // interface lowering pass to assign type codes as expected by the reflect
// package. See getTypeCodeNum. // package. See getTypeCodeNum.
func getTypeCodeName(t types.Type) string { func getTypeCodeName(t types.Type) string {
name := ""
if named, ok := t.(*types.Named); ok {
name = "~" + named.String() + ":"
t = t.Underlying()
}
switch t := t.(type) { switch t := t.(type) {
case *types.Named:
return "named:" + t.String()
case *types.Array: case *types.Array:
return "array:" + name + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem()) return "array:" + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem())
case *types.Basic: case *types.Basic:
var kind string var kind string
switch t.Kind() { switch t.Kind() {
@ -108,21 +127,21 @@ func getTypeCodeName(t types.Type) string {
default: default:
panic("unknown basic type: " + t.Name()) panic("unknown basic type: " + t.Name())
} }
return "basic:" + name + kind return "basic:" + kind
case *types.Chan: case *types.Chan:
return "chan:" + name + getTypeCodeName(t.Elem()) return "chan:" + getTypeCodeName(t.Elem())
case *types.Interface: case *types.Interface:
methods := make([]string, t.NumMethods()) methods := make([]string, t.NumMethods())
for i := 0; i < t.NumMethods(); i++ { for i := 0; i < t.NumMethods(); i++ {
methods[i] = getTypeCodeName(t.Method(i).Type()) methods[i] = getTypeCodeName(t.Method(i).Type())
} }
return "interface:" + name + "{" + strings.Join(methods, ",") + "}" return "interface:" + "{" + strings.Join(methods, ",") + "}"
case *types.Map: case *types.Map:
keyType := getTypeCodeName(t.Key()) keyType := getTypeCodeName(t.Key())
elemType := getTypeCodeName(t.Elem()) elemType := getTypeCodeName(t.Elem())
return "map:" + name + "{" + keyType + "," + elemType + "}" return "map:" + "{" + keyType + "," + elemType + "}"
case *types.Pointer: case *types.Pointer:
return "pointer:" + name + getTypeCodeName(t.Elem()) return "pointer:" + getTypeCodeName(t.Elem())
case *types.Signature: case *types.Signature:
params := make([]string, t.Params().Len()) params := make([]string, t.Params().Len())
for i := 0; i < t.Params().Len(); i++ { for i := 0; i < t.Params().Len(); i++ {
@ -132,9 +151,9 @@ func getTypeCodeName(t types.Type) string {
for i := 0; i < t.Results().Len(); i++ { for i := 0; i < t.Results().Len(); i++ {
results[i] = getTypeCodeName(t.Results().At(i).Type()) results[i] = getTypeCodeName(t.Results().At(i).Type())
} }
return "func:" + name + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}" return "func:" + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}"
case *types.Slice: case *types.Slice:
return "slice:" + name + getTypeCodeName(t.Elem()) return "slice:" + getTypeCodeName(t.Elem())
case *types.Struct: case *types.Struct:
elems := make([]string, t.NumFields()) elems := make([]string, t.NumFields())
if t.NumFields() > 2 && t.Field(0).Name() == "C union" { if t.NumFields() > 2 && t.Field(0).Name() == "C union" {
@ -144,7 +163,7 @@ func getTypeCodeName(t types.Type) string {
for i := 0; i < t.NumFields(); i++ { for i := 0; i < t.NumFields(); i++ {
elems[i] = getTypeCodeName(t.Field(i).Type()) elems[i] = getTypeCodeName(t.Field(i).Type())
} }
return "struct:" + name + "{" + strings.Join(elems, ",") + "}" return "struct:" + "{" + strings.Join(elems, ",") + "}"
default: default:
panic("unknown type: " + t.String()) panic("unknown type: " + t.String())
} }

Просмотреть файл

@ -3,6 +3,8 @@ package compiler
import ( import (
"math/big" "math/big"
"strings" "strings"
"tinygo.org/x/go-llvm"
) )
var basicTypes = map[string]int64{ var basicTypes = map[string]int64{
@ -41,10 +43,7 @@ func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
fallbackIndex := 1 fallbackIndex := 1
namedTypes := make(map[string]int) namedTypes := make(map[string]int)
for _, t := range typeSlice { for _, t := range typeSlice {
if t.name[:5] != "type:" { num := c.getTypeCodeNum(t.typecode, &fallbackIndex, namedTypes)
panic("expected type name to start with 'type:'")
}
num := c.getTypeCodeNum(t.name[5:], &fallbackIndex, namedTypes)
if num.BitLen() > c.uintptrType.IntTypeWidth() || !num.IsUint64() { if num.BitLen() > c.uintptrType.IntTypeWidth() || !num.IsUint64() {
// TODO: support this in some way, using a side table for example. // TODO: support this in some way, using a side table for example.
// That's less efficient but better than not working at all. // That's less efficient but better than not working at all.
@ -59,20 +58,14 @@ func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
// getTypeCodeNum returns the typecode for a given type as expected by the // getTypeCodeNum returns the typecode for a given type as expected by the
// reflect package. Also see getTypeCodeName, which serializes types to a string // reflect package. Also see getTypeCodeName, which serializes types to a string
// based on a types.Type value for this function. // based on a types.Type value for this function.
func (c *Compiler) getTypeCodeNum(id string, fallbackIndex *int, namedTypes map[string]int) *big.Int { func (c *Compiler) getTypeCodeNum(typecode llvm.Value, fallbackIndex *int, namedTypes map[string]int) *big.Int {
// Note: see src/reflect/type.go for bit allocations. // Note: see src/reflect/type.go for bit allocations.
// A type can be named or unnamed. Example of both: class, value := getClassAndValueFromTypeCode(typecode)
// basic:~foo:uint64
// basic:uint64
// Extract the class (basic, slice, pointer, etc.), the name, and the
// contents of this type ID string. Allocate bits based on that, as
// src/runtime/types.go expects.
class := id[:strings.IndexByte(id, ':')]
value := id[len(class)+1:]
name := "" name := ""
if value[0] == '~' { if class == "named" {
name = value[1:strings.IndexByte(value, ':')] name = value
value = value[len(name)+2:] typecode = llvm.ConstExtractValue(typecode.Initializer(), []uint32{0})
class, value = getClassAndValueFromTypeCode(typecode)
} }
if class == "basic" { if class == "basic" {
// Basic types follow the following bit pattern: // Basic types follow the following bit pattern:
@ -81,7 +74,7 @@ func (c *Compiler) getTypeCodeNum(id string, fallbackIndex *int, namedTypes map[
// upper bits are used to indicate the named type. // upper bits are used to indicate the named type.
num, ok := basicTypes[value] num, ok := basicTypes[value]
if !ok { if !ok {
panic("invalid basic type: " + id) panic("invalid basic type: " + value)
} }
if name != "" { if name != "" {
// This type is named, set the upper bits to the name ID. // This type is named, set the upper bits to the name ID.
@ -100,17 +93,20 @@ func (c *Compiler) getTypeCodeNum(id string, fallbackIndex *int, namedTypes map[
var classNumber int64 var classNumber int64
switch class { switch class {
case "chan": case "chan":
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes) sub := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0})
num = c.getTypeCodeNum(sub, fallbackIndex, namedTypes)
classNumber = 0 classNumber = 0
case "interface": case "interface":
num = big.NewInt(int64(*fallbackIndex)) num = big.NewInt(int64(*fallbackIndex))
*fallbackIndex++ *fallbackIndex++
classNumber = 1 classNumber = 1
case "pointer": case "pointer":
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes) sub := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0})
num = c.getTypeCodeNum(sub, fallbackIndex, namedTypes)
classNumber = 2 classNumber = 2
case "slice": case "slice":
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes) sub := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0})
num = c.getTypeCodeNum(sub, fallbackIndex, namedTypes)
classNumber = 3 classNumber = 3
case "array": case "array":
num = big.NewInt(int64(*fallbackIndex)) num = big.NewInt(int64(*fallbackIndex))
@ -129,7 +125,7 @@ func (c *Compiler) getTypeCodeNum(id string, fallbackIndex *int, namedTypes map[
*fallbackIndex++ *fallbackIndex++
classNumber = 7 classNumber = 7
default: default:
panic("unknown type kind: " + id) panic("unknown type kind: " + class)
} }
if name == "" { if name == "" {
num.Lsh(num, 5).Or(num, big.NewInt((classNumber<<1)+1)) num.Lsh(num, 5).Or(num, big.NewInt((classNumber<<1)+1))
@ -142,6 +138,25 @@ func (c *Compiler) getTypeCodeNum(id string, fallbackIndex *int, namedTypes map[
} }
} }
// getClassAndValueFromTypeCode takes a typecode (a llvm.Value of type
// runtime.typecodeID), looks at the name, and extracts the typecode class and
// value from it. For example, for a typecode with the following name:
// reflect/types.type:pointer:named:reflect.ValueError
// It extracts:
// class = "pointer"
// value = "named:reflect.ValueError"
func getClassAndValueFromTypeCode(typecode llvm.Value) (class, value string) {
typecodeName := typecode.Name()
const prefix = "reflect/types.type:"
if !strings.HasPrefix(typecodeName, prefix) {
panic("unexpected typecode name: " + typecodeName)
}
id := typecodeName[len(prefix):]
class = id[:strings.IndexByte(id, ':')]
value = id[len(class)+1:]
return
}
// getNamedTypeNum returns an appropriate (unique) number for the given named // getNamedTypeNum returns an appropriate (unique) number for the given named
// type. If the name already has a number that number is returned, else a new // type. If the name already has a number that number is returned, else a new
// number is returned. The number is always non-zero. // number is returned. The number is always non-zero.

Просмотреть файл

@ -16,13 +16,13 @@ type funcValue struct {
// funcValueWithSignature is used before the func lowering pass. // funcValueWithSignature is used before the func lowering pass.
type funcValueWithSignature struct { type funcValueWithSignature struct {
funcPtr uintptr // ptrtoint of the actual function pointer funcPtr uintptr // ptrtoint of the actual function pointer
signature *uint8 // pointer to identify this signature (the value is undef) signature *typecodeID // pointer to identify this signature (the value is undef)
} }
// getFuncPtr is a dummy function that may be used if the func lowering pass is // getFuncPtr is a dummy function that may be used if the func lowering pass is
// not used. It is generally too slow but may be a useful fallback to debug the // not used. It is generally too slow but may be a useful fallback to debug the
// func lowering pass. // func lowering pass.
func getFuncPtr(val funcValue, signature *uint8) uintptr { func getFuncPtr(val funcValue, signature *typecodeID) uintptr {
return (*funcValueWithSignature)(unsafe.Pointer(val.id)).funcPtr return (*funcValueWithSignature)(unsafe.Pointer(val.id)).funcPtr
} }

Просмотреть файл

@ -43,7 +43,16 @@ type interfaceMethodInfo struct {
funcptr uintptr // bitcast from the actual function pointer funcptr uintptr // bitcast from the actual function pointer
} }
type typecodeID struct{} type typecodeID struct {
// Depending on the type kind of this typecodeID, this pointer is something
// different:
// * basic types: null
// * named type: the underlying type
// * interface: null
// * chan/pointer/slice: the element type
// * array/func/map/struct: TODO
references *typecodeID
}
// Pseudo type used before interface lowering. By using a struct instead of a // Pseudo type used before interface lowering. By using a struct instead of a
// function call, this is simpler to reason about during init interpretation // function call, this is simpler to reason about during init interpretation

8
testdata/interface.go предоставленный
Просмотреть файл

@ -22,6 +22,10 @@ func main() {
println("Stringer.(*Thing).String():", itf.(Stringer).String()) println("Stringer.(*Thing).String():", itf.(Stringer).String())
println("nested switch:", nestedSwitch('v', 3)) println("nested switch:", nestedSwitch('v', 3))
// Try putting a linked list in an interface:
// https://github.com/tinygo-org/tinygo/issues/309
itf = linkedList{}
} }
func printItf(val interface{}) { func printItf(val interface{}) {
@ -134,3 +138,7 @@ func (p SmallPair) Print() {
type Unmatched interface { type Unmatched interface {
NeverImplementedMethod() NeverImplementedMethod()
} }
type linkedList struct {
addr *linkedList
}