Skip to content

Commit

Permalink
compiler: add parameter names to IR
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
aykevl authored and deadprogram committed Apr 21, 2020
1 parent f00bb63 commit 16c2d84
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 42 deletions.
83 changes: 61 additions & 22 deletions compiler/calls.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package compiler

import (
"go/types"
"strconv"

"tinygo.org/x/go-llvm"
)
Expand All @@ -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
Expand Down Expand Up @@ -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),
},
}
}

Expand Down Expand Up @@ -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
Expand All @@ -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,
},
}
}
}

Expand Down Expand Up @@ -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() {
Expand Down
33 changes: 18 additions & 15 deletions compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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]&paramIsDeferenceableOrNull == 0 {
for i, info := range paramInfos {
if info.flags&paramIsDeferenceableOrNull == 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.
Expand Down Expand Up @@ -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)
Expand Down
10 changes: 6 additions & 4 deletions compiler/func.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion compiler/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 16c2d84

Please sign in to comment.