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.
Этот коммит содержится в:
родитель
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.
|
||||||
|
|
|
@ -17,12 +17,12 @@ 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
предоставленный
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
|
||||||
|
}
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче