From 16c2d84c49b7192755dd88ce3f79132ee10c1ff9 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 9 Apr 2020 23:27:18 +0200 Subject: [PATCH] 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. --- compiler/calls.go | 83 +++++++++++++++++++++++++++++++------------ compiler/compiler.go | 33 +++++++++-------- compiler/func.go | 10 +++--- compiler/interface.go | 5 ++- 4 files changed, 89 insertions(+), 42 deletions(-) diff --git a/compiler/calls.go b/compiler/calls.go index e11913c9..efe96d52 100644 --- a/compiler/calls.go +++ b/compiler/calls.go @@ -2,6 +2,7 @@ package compiler import ( "go/types" + "strconv" "tinygo.org/x/go-llvm" ) @@ -13,6 +14,14 @@ import ( // a struct contains more fields, it is passed as a struct without expanding. 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 // determines which parameters are dereferenceable_or_null and which aren't. 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 // 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() { case llvm.StructTypeKind: - fields, fieldFlags := flattenAggregateType(t, goType) - if len(fields) <= maxFieldsPerParam { - return fields, fieldFlags + fieldInfos := flattenAggregateType(t, name, goType) + if len(fieldInfos) <= maxFieldsPerParam { + return fieldInfos } else { // failed to lower - return []llvm.Type{t}, []paramFlags{getTypeFlags(goType)} } - default: - // TODO: split small arrays - return []llvm.Type{t}, []paramFlags{getTypeFlags(goType)} + } + // TODO: split small arrays + 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 { switch v.Type().TypeKind() { case llvm.StructTypeKind: - fieldTypes, _ := flattenAggregateType(v.Type(), nil) - if len(fieldTypes) <= maxFieldsPerParam { + fieldInfos := flattenAggregateType(v.Type(), "", nil) + if len(fieldInfos) <= maxFieldsPerParam { fields := b.flattenAggregate(v) - if len(fields) != len(fieldTypes) { + if len(fields) != len(fieldInfos) { panic("type and value param lowering don't match") } 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 // 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) switch t.TypeKind() { case llvm.StructTypeKind: - fields := make([]llvm.Type, 0, t.StructElementTypesCount()) - fieldFlags := make([]paramFlags, 0, cap(fields)) + paramInfos := make([]paramInfo, 0, t.StructElementTypesCount()) for i, subfield := range t.StructElementTypes() { - subfields, subfieldFlags := flattenAggregateType(subfield, extractSubfield(goType, i)) - for i := range subfieldFlags { - subfieldFlags[i] |= typeFlags + suffix := strconv.Itoa(i) + if goType != nil { + // 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] + } + case *types.Signature: + suffix = []string{"context", "funcptr"}[i] + } } - fields = append(fields, subfields...) - fieldFlags = append(fieldFlags, subfieldFlags...) + subInfos := flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i)) + for i := range subInfos { + subInfos[i].flags |= typeFlags + } + paramInfos = append(paramInfos, subInfos...) } - return fields, fieldFlags + return paramInfos 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) { switch t.TypeKind() { case llvm.StructTypeKind: - flattened, _ := flattenAggregateType(t, nil) + flattened := flattenAggregateType(t, "", nil) if len(flattened) <= maxFieldsPerParam { value := llvm.ConstNull(t) for i, subtyp := range t.StructElementTypes() { diff --git a/compiler/compiler.go b/compiler/compiler.go index 55fa1e65..6ab553b4 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -738,21 +738,23 @@ func (c *compilerContext) createFunctionDeclaration(f *ir.Function) { retType = c.ctx.StructType(results, false) } - var paramTypes []llvm.Type - var paramTypeVariants []paramFlags + var paramInfos []paramInfo for _, param := range f.Params { paramType := c.getLLVMType(param.Type()) - paramTypeFragments, paramTypeFragmentVariants := expandFormalParamType(paramType, param.Type()) - paramTypes = append(paramTypes, paramTypeFragments...) - paramTypeVariants = append(paramTypeVariants, paramTypeFragmentVariants...) + paramFragmentInfos := expandFormalParamType(paramType, param.Name(), param.Type()) + paramInfos = append(paramInfos, paramFragmentInfos...) } // 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. if !f.IsExported() { - paramTypes = append(paramTypes, c.i8ptrType) // context - paramTypes = append(paramTypes, c.i8ptrType) // parent coroutine - paramTypeVariants = append(paramTypeVariants, 0, 0) + paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "context", flags: 0}) + paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "parentHandle", flags: 0}) + } + + var paramTypes []llvm.Type + for _, info := range paramInfos { + paramTypes = append(paramTypes, info.llvmType) } fnType := llvm.FunctionType(retType, paramTypes, false) @@ -764,12 +766,12 @@ func (c *compilerContext) createFunctionDeclaration(f *ir.Function) { } dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null") - for i, typ := range paramTypes { - if paramTypeVariants[i]¶mIsDeferenceableOrNull == 0 { + for i, info := range paramInfos { + if info.flags¶mIsDeferenceableOrNull == 0 { continue } - if typ.TypeKind() == llvm.PointerTypeKind { - el := typ.ElementType() + if info.llvmType.TypeKind() == llvm.PointerTypeKind { + el := info.llvmType.ElementType() size := c.targetData.TypeAllocSize(el) if size == 0 { // dereferenceable_or_null(0) appears to be illegal in LLVM. @@ -911,9 +913,10 @@ func (b *builder) createFunctionDefinition() { for _, param := range b.fn.Params { llvmType := b.getLLVMType(param.Type()) fields := make([]llvm.Value, 0, 1) - fieldFragments, _ := expandFormalParamType(llvmType, nil) - for range fieldFragments { - fields = append(fields, b.fn.LLVMFn.Param(llvmParamIndex)) + for _, info := range expandFormalParamType(llvmType, param.Name(), param.Type()) { + param := b.fn.LLVMFn.Param(llvmParamIndex) + param.SetName(info.name) + fields = append(fields, param) llvmParamIndex++ } b.locals[param] = b.collapseFormalParam(llvmType, fields) diff --git a/compiler/func.go b/compiler/func.go index 2d14d47a..463350aa 100644 --- a/compiler/func.go +++ b/compiler/func.go @@ -125,13 +125,15 @@ func (c *compilerContext) getRawFuncType(typ *types.Signature) llvm.Type { // The receiver is not an interface, but a i8* type. recv = c.i8ptrType } - recvFragments, _ := expandFormalParamType(recv, nil) - paramTypes = append(paramTypes, recvFragments...) + for _, info := range expandFormalParamType(recv, "", nil) { + paramTypes = append(paramTypes, info.llvmType) + } } for i := 0; i < typ.Params().Len(); i++ { subType := c.getLLVMType(typ.Params().At(i).Type()) - paramTypeFragments, _ := expandFormalParamType(subType, nil) - paramTypes = append(paramTypes, paramTypeFragments...) + for _, info := range expandFormalParamType(subType, "", nil) { + paramTypes = append(paramTypes, info.llvmType) + } } // All functions take these parameters at the end. paramTypes = append(paramTypes, c.i8ptrType) // context diff --git a/compiler/interface.go b/compiler/interface.go index fd48f261..fa08c5b0 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -446,7 +446,10 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value { // Get the expanded receiver 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? if len(expandedReceiverType) == 1 && receiverType.TypeKind() == llvm.PointerTypeKind {