compiler: add parameter names to IR

This makes viewing the IR easier because parameters have readable names.

This also makes it easier to write compiler tests (still a work in
progress), that work in LLVM 9 and LLVM 10, as LLVM 10 started printing
value names for unnamed parameters.
Этот коммит содержится в:
Ayke van Laethem 2020-04-09 23:27:18 +02:00 коммит произвёл Ron Evans
родитель f00bb63330
коммит 16c2d84c49
4 изменённых файлов: 89 добавлений и 42 удалений

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

@ -2,6 +2,7 @@ package compiler
import ( import (
"go/types" "go/types"
"strconv"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@ -13,6 +14,14 @@ import (
// a struct contains more fields, it is passed as a struct without expanding. // a struct contains more fields, it is passed as a struct without expanding.
const maxFieldsPerParam = 3 const maxFieldsPerParam = 3
// paramInfo contains some information collected about a function parameter,
// useful while declaring or defining a function.
type paramInfo struct {
llvmType llvm.Type
name string // name, possibly with suffixes for e.g. struct fields
flags paramFlags
}
// paramFlags identifies parameter attributes for flags. Most importantly, it // paramFlags identifies parameter attributes for flags. Most importantly, it
// determines which parameters are dereferenceable_or_null and which aren't. // determines which parameters are dereferenceable_or_null and which aren't.
type paramFlags uint8 type paramFlags uint8
@ -48,19 +57,23 @@ func (b *builder) createCall(fn llvm.Value, args []llvm.Value, name string) llvm
// Expand an argument type to a list that can be used in a function call // Expand an argument type to a list that can be used in a function call
// parameter list. // parameter list.
func expandFormalParamType(t llvm.Type, goType types.Type) ([]llvm.Type, []paramFlags) { func expandFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo {
switch t.TypeKind() { switch t.TypeKind() {
case llvm.StructTypeKind: case llvm.StructTypeKind:
fields, fieldFlags := flattenAggregateType(t, goType) fieldInfos := flattenAggregateType(t, name, goType)
if len(fields) <= maxFieldsPerParam { if len(fieldInfos) <= maxFieldsPerParam {
return fields, fieldFlags return fieldInfos
} else { } else {
// failed to lower // failed to lower
return []llvm.Type{t}, []paramFlags{getTypeFlags(goType)}
} }
default: }
// TODO: split small arrays // TODO: split small arrays
return []llvm.Type{t}, []paramFlags{getTypeFlags(goType)} return []paramInfo{
{
llvmType: t,
name: name,
flags: getTypeFlags(goType),
},
} }
} }
@ -91,10 +104,10 @@ func (b *builder) expandFormalParamOffsets(t llvm.Type) []uint64 {
func (b *builder) expandFormalParam(v llvm.Value) []llvm.Value { func (b *builder) expandFormalParam(v llvm.Value) []llvm.Value {
switch v.Type().TypeKind() { switch v.Type().TypeKind() {
case llvm.StructTypeKind: case llvm.StructTypeKind:
fieldTypes, _ := flattenAggregateType(v.Type(), nil) fieldInfos := flattenAggregateType(v.Type(), "", nil)
if len(fieldTypes) <= maxFieldsPerParam { if len(fieldInfos) <= maxFieldsPerParam {
fields := b.flattenAggregate(v) fields := b.flattenAggregate(v)
if len(fields) != len(fieldTypes) { if len(fields) != len(fieldInfos) {
panic("type and value param lowering don't match") panic("type and value param lowering don't match")
} }
return fields return fields
@ -110,23 +123,49 @@ func (b *builder) expandFormalParam(v llvm.Value) []llvm.Value {
// Try to flatten a struct type to a list of types. Returns a 1-element slice // Try to flatten a struct type to a list of types. Returns a 1-element slice
// with the passed in type if this is not possible. // with the passed in type if this is not possible.
func flattenAggregateType(t llvm.Type, goType types.Type) ([]llvm.Type, []paramFlags) { func flattenAggregateType(t llvm.Type, name string, goType types.Type) []paramInfo {
typeFlags := getTypeFlags(goType) typeFlags := getTypeFlags(goType)
switch t.TypeKind() { switch t.TypeKind() {
case llvm.StructTypeKind: case llvm.StructTypeKind:
fields := make([]llvm.Type, 0, t.StructElementTypesCount()) paramInfos := make([]paramInfo, 0, t.StructElementTypesCount())
fieldFlags := make([]paramFlags, 0, cap(fields))
for i, subfield := range t.StructElementTypes() { for i, subfield := range t.StructElementTypes() {
subfields, subfieldFlags := flattenAggregateType(subfield, extractSubfield(goType, i)) suffix := strconv.Itoa(i)
for i := range subfieldFlags { if goType != nil {
subfieldFlags[i] |= typeFlags // Try to come up with a good suffix for this struct field,
// depending on which Go type it's based on.
switch goType := goType.Underlying().(type) {
case *types.Interface:
suffix = []string{"typecode", "value"}[i]
case *types.Slice:
suffix = []string{"data", "len", "cap"}[i]
case *types.Struct:
suffix = goType.Field(i).Name()
case *types.Basic:
switch goType.Kind() {
case types.Complex64, types.Complex128:
suffix = []string{"r", "i"}[i]
case types.String:
suffix = []string{"data", "len"}[i]
} }
fields = append(fields, subfields...) case *types.Signature:
fieldFlags = append(fieldFlags, subfieldFlags...) suffix = []string{"context", "funcptr"}[i]
} }
return fields, fieldFlags }
subInfos := flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i))
for i := range subInfos {
subInfos[i].flags |= typeFlags
}
paramInfos = append(paramInfos, subInfos...)
}
return paramInfos
default: default:
return []llvm.Type{t}, []paramFlags{typeFlags} return []paramInfo{
{
llvmType: t,
name: name,
flags: typeFlags,
},
}
} }
} }
@ -226,7 +265,7 @@ func (b *builder) collapseFormalParam(t llvm.Type, fields []llvm.Value) llvm.Val
func (b *builder) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value) (llvm.Value, []llvm.Value) { func (b *builder) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value) (llvm.Value, []llvm.Value) {
switch t.TypeKind() { switch t.TypeKind() {
case llvm.StructTypeKind: case llvm.StructTypeKind:
flattened, _ := flattenAggregateType(t, nil) flattened := flattenAggregateType(t, "", nil)
if len(flattened) <= maxFieldsPerParam { if len(flattened) <= maxFieldsPerParam {
value := llvm.ConstNull(t) value := llvm.ConstNull(t)
for i, subtyp := range t.StructElementTypes() { for i, subtyp := range t.StructElementTypes() {

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

@ -738,21 +738,23 @@ func (c *compilerContext) createFunctionDeclaration(f *ir.Function) {
retType = c.ctx.StructType(results, false) retType = c.ctx.StructType(results, false)
} }
var paramTypes []llvm.Type var paramInfos []paramInfo
var paramTypeVariants []paramFlags
for _, param := range f.Params { for _, param := range f.Params {
paramType := c.getLLVMType(param.Type()) paramType := c.getLLVMType(param.Type())
paramTypeFragments, paramTypeFragmentVariants := expandFormalParamType(paramType, param.Type()) paramFragmentInfos := expandFormalParamType(paramType, param.Name(), param.Type())
paramTypes = append(paramTypes, paramTypeFragments...) paramInfos = append(paramInfos, paramFragmentInfos...)
paramTypeVariants = append(paramTypeVariants, paramTypeFragmentVariants...)
} }
// Add an extra parameter as the function context. This context is used in // Add an extra parameter as the function context. This context is used in
// closures and bound methods, but should be optimized away when not used. // closures and bound methods, but should be optimized away when not used.
if !f.IsExported() { if !f.IsExported() {
paramTypes = append(paramTypes, c.i8ptrType) // context paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "context", flags: 0})
paramTypes = append(paramTypes, c.i8ptrType) // parent coroutine paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "parentHandle", flags: 0})
paramTypeVariants = append(paramTypeVariants, 0, 0) }
var paramTypes []llvm.Type
for _, info := range paramInfos {
paramTypes = append(paramTypes, info.llvmType)
} }
fnType := llvm.FunctionType(retType, paramTypes, false) fnType := llvm.FunctionType(retType, paramTypes, false)
@ -764,12 +766,12 @@ func (c *compilerContext) createFunctionDeclaration(f *ir.Function) {
} }
dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null") dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null")
for i, typ := range paramTypes { for i, info := range paramInfos {
if paramTypeVariants[i]&paramIsDeferenceableOrNull == 0 { if info.flags&paramIsDeferenceableOrNull == 0 {
continue continue
} }
if typ.TypeKind() == llvm.PointerTypeKind { if info.llvmType.TypeKind() == llvm.PointerTypeKind {
el := typ.ElementType() el := info.llvmType.ElementType()
size := c.targetData.TypeAllocSize(el) size := c.targetData.TypeAllocSize(el)
if size == 0 { if size == 0 {
// dereferenceable_or_null(0) appears to be illegal in LLVM. // dereferenceable_or_null(0) appears to be illegal in LLVM.
@ -911,9 +913,10 @@ func (b *builder) createFunctionDefinition() {
for _, param := range b.fn.Params { for _, param := range b.fn.Params {
llvmType := b.getLLVMType(param.Type()) llvmType := b.getLLVMType(param.Type())
fields := make([]llvm.Value, 0, 1) fields := make([]llvm.Value, 0, 1)
fieldFragments, _ := expandFormalParamType(llvmType, nil) for _, info := range expandFormalParamType(llvmType, param.Name(), param.Type()) {
for range fieldFragments { param := b.fn.LLVMFn.Param(llvmParamIndex)
fields = append(fields, b.fn.LLVMFn.Param(llvmParamIndex)) param.SetName(info.name)
fields = append(fields, param)
llvmParamIndex++ llvmParamIndex++
} }
b.locals[param] = b.collapseFormalParam(llvmType, fields) b.locals[param] = b.collapseFormalParam(llvmType, fields)

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

@ -125,13 +125,15 @@ func (c *compilerContext) getRawFuncType(typ *types.Signature) llvm.Type {
// The receiver is not an interface, but a i8* type. // The receiver is not an interface, but a i8* type.
recv = c.i8ptrType recv = c.i8ptrType
} }
recvFragments, _ := expandFormalParamType(recv, nil) for _, info := range expandFormalParamType(recv, "", nil) {
paramTypes = append(paramTypes, recvFragments...) paramTypes = append(paramTypes, info.llvmType)
}
} }
for i := 0; i < typ.Params().Len(); i++ { for i := 0; i < typ.Params().Len(); i++ {
subType := c.getLLVMType(typ.Params().At(i).Type()) subType := c.getLLVMType(typ.Params().At(i).Type())
paramTypeFragments, _ := expandFormalParamType(subType, nil) for _, info := range expandFormalParamType(subType, "", nil) {
paramTypes = append(paramTypes, paramTypeFragments...) paramTypes = append(paramTypes, info.llvmType)
}
} }
// All functions take these parameters at the end. // All functions take these parameters at the end.
paramTypes = append(paramTypes, c.i8ptrType) // context paramTypes = append(paramTypes, c.i8ptrType) // context

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

@ -446,7 +446,10 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
// Get the expanded receiver type. // Get the expanded receiver type.
receiverType := c.getLLVMType(f.Params[0].Type()) receiverType := c.getLLVMType(f.Params[0].Type())
expandedReceiverType, _ := expandFormalParamType(receiverType, nil) var expandedReceiverType []llvm.Type
for _, info := range expandFormalParamType(receiverType, "", nil) {
expandedReceiverType = append(expandedReceiverType, info.llvmType)
}
// Does this method even need any wrapping? // Does this method even need any wrapping?
if len(expandedReceiverType) == 1 && receiverType.TypeKind() == llvm.PointerTypeKind { if len(expandedReceiverType) == 1 && receiverType.TypeKind() == llvm.PointerTypeKind {