github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/compiler/calls.go (about)

     1  package compiler
     2  
     3  import (
     4  	"go/types"
     5  	"strconv"
     6  
     7  	"golang.org/x/tools/go/ssa"
     8  	"tinygo.org/x/go-llvm"
     9  )
    10  
    11  // For a description of the calling convention in prose, see:
    12  // https://tinygo.org/compiler-internals/calling-convention/
    13  
    14  // The maximum number of arguments that can be expanded from a single struct. If
    15  // a struct contains more fields, it is passed as a struct without expanding.
    16  const maxFieldsPerParam = 3
    17  
    18  // paramInfo contains some information collected about a function parameter,
    19  // useful while declaring or defining a function.
    20  type paramInfo struct {
    21  	llvmType llvm.Type
    22  	name     string // name, possibly with suffixes for e.g. struct fields
    23  	elemSize uint64 // size of pointer element type, or 0 if this isn't a pointer
    24  }
    25  
    26  // paramFlags identifies parameter attributes for flags. Most importantly, it
    27  // determines which parameters are dereferenceable_or_null and which aren't.
    28  type paramFlags uint8
    29  
    30  const (
    31  	// Parameter may have the deferenceable_or_null attribute. This attribute
    32  	// cannot be applied to unsafe.Pointer and to the data pointer of slices.
    33  	paramIsDeferenceableOrNull = 1 << iota
    34  )
    35  
    36  // createRuntimeCallCommon creates a runtime call. Use createRuntimeCall or
    37  // createRuntimeInvoke instead.
    38  func (b *builder) createRuntimeCallCommon(fnName string, args []llvm.Value, name string, isInvoke bool) llvm.Value {
    39  	member := b.program.ImportedPackage("runtime").Members[fnName]
    40  	if member == nil {
    41  		panic("unknown runtime call: " + fnName)
    42  	}
    43  	fn := member.(*ssa.Function)
    44  	fnType, llvmFn := b.getFunction(fn)
    45  	if llvmFn.IsNil() {
    46  		panic("trying to call non-existent function: " + fn.RelString(nil))
    47  	}
    48  	args = append(args, llvm.Undef(b.dataPtrType)) // unused context parameter
    49  	if isInvoke {
    50  		return b.createInvoke(fnType, llvmFn, args, name)
    51  	}
    52  	return b.createCall(fnType, llvmFn, args, name)
    53  }
    54  
    55  // createRuntimeCall creates a new call to runtime.<fnName> with the given
    56  // arguments.
    57  func (b *builder) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value {
    58  	return b.createRuntimeCallCommon(fnName, args, name, false)
    59  }
    60  
    61  // createRuntimeInvoke creates a new call to runtime.<fnName> with the given
    62  // arguments. If the runtime call panics, control flow is diverted to the
    63  // landing pad block.
    64  // Note that "invoke" here is meant in the LLVM sense (a call that can
    65  // panic/throw), not in the Go sense (an interface method call).
    66  func (b *builder) createRuntimeInvoke(fnName string, args []llvm.Value, name string) llvm.Value {
    67  	return b.createRuntimeCallCommon(fnName, args, name, true)
    68  }
    69  
    70  // createCall creates a call to the given function with the arguments possibly
    71  // expanded.
    72  func (b *builder) createCall(fnType llvm.Type, fn llvm.Value, args []llvm.Value, name string) llvm.Value {
    73  	expanded := make([]llvm.Value, 0, len(args))
    74  	for _, arg := range args {
    75  		fragments := b.expandFormalParam(arg)
    76  		expanded = append(expanded, fragments...)
    77  	}
    78  	return b.CreateCall(fnType, fn, expanded, name)
    79  }
    80  
    81  // createInvoke is like createCall but continues execution at the landing pad if
    82  // the call resulted in a panic.
    83  func (b *builder) createInvoke(fnType llvm.Type, fn llvm.Value, args []llvm.Value, name string) llvm.Value {
    84  	if b.hasDeferFrame() {
    85  		b.createInvokeCheckpoint()
    86  	}
    87  	return b.createCall(fnType, fn, args, name)
    88  }
    89  
    90  // Expand an argument type to a list that can be used in a function call
    91  // parameter list.
    92  func (c *compilerContext) expandFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo {
    93  	switch t.TypeKind() {
    94  	case llvm.StructTypeKind:
    95  		fieldInfos := c.flattenAggregateType(t, name, goType)
    96  		if len(fieldInfos) <= maxFieldsPerParam {
    97  			// managed to expand this parameter
    98  			return fieldInfos
    99  		}
   100  		// failed to expand this parameter: too many fields
   101  	}
   102  	// TODO: split small arrays
   103  	return []paramInfo{c.getParamInfo(t, name, goType)}
   104  }
   105  
   106  // expandFormalParamOffsets returns a list of offsets from the start of an
   107  // object of type t after it would have been split up by expandFormalParam. This
   108  // is useful for debug information, where it is necessary to know the offset
   109  // from the start of the combined object.
   110  func (b *builder) expandFormalParamOffsets(t llvm.Type) []uint64 {
   111  	switch t.TypeKind() {
   112  	case llvm.StructTypeKind:
   113  		fields := b.flattenAggregateTypeOffsets(t)
   114  		if len(fields) <= maxFieldsPerParam {
   115  			return fields
   116  		} else {
   117  			// failed to lower
   118  			return []uint64{0}
   119  		}
   120  	default:
   121  		// TODO: split small arrays
   122  		return []uint64{0}
   123  	}
   124  }
   125  
   126  // expandFormalParam splits a formal param value into pieces, so it can be
   127  // passed directly as part of a function call. For example, it splits up small
   128  // structs into individual fields. It is the equivalent of expandFormalParamType
   129  // for parameter values.
   130  func (b *builder) expandFormalParam(v llvm.Value) []llvm.Value {
   131  	switch v.Type().TypeKind() {
   132  	case llvm.StructTypeKind:
   133  		fieldInfos := b.flattenAggregateType(v.Type(), "", nil)
   134  		if len(fieldInfos) <= maxFieldsPerParam {
   135  			fields := b.flattenAggregate(v)
   136  			if len(fields) != len(fieldInfos) {
   137  				panic("type and value param lowering don't match")
   138  			}
   139  			return fields
   140  		} else {
   141  			// failed to lower
   142  			return []llvm.Value{v}
   143  		}
   144  	default:
   145  		// TODO: split small arrays
   146  		return []llvm.Value{v}
   147  	}
   148  }
   149  
   150  // Try to flatten a struct type to a list of types. Returns a 1-element slice
   151  // with the passed in type if this is not possible.
   152  func (c *compilerContext) flattenAggregateType(t llvm.Type, name string, goType types.Type) []paramInfo {
   153  	switch t.TypeKind() {
   154  	case llvm.StructTypeKind:
   155  		var paramInfos []paramInfo
   156  		for i, subfield := range t.StructElementTypes() {
   157  			if c.targetData.TypeAllocSize(subfield) == 0 {
   158  				continue
   159  			}
   160  			suffix := strconv.Itoa(i)
   161  			if goType != nil {
   162  				// Try to come up with a good suffix for this struct field,
   163  				// depending on which Go type it's based on.
   164  				switch goType := goType.Underlying().(type) {
   165  				case *types.Interface:
   166  					suffix = []string{"typecode", "value"}[i]
   167  				case *types.Slice:
   168  					suffix = []string{"data", "len", "cap"}[i]
   169  				case *types.Struct:
   170  					suffix = goType.Field(i).Name()
   171  				case *types.Basic:
   172  					switch goType.Kind() {
   173  					case types.Complex64, types.Complex128:
   174  						suffix = []string{"r", "i"}[i]
   175  					case types.String:
   176  						suffix = []string{"data", "len"}[i]
   177  					}
   178  				case *types.Signature:
   179  					suffix = []string{"context", "funcptr"}[i]
   180  				}
   181  			}
   182  			subInfos := c.flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i))
   183  			paramInfos = append(paramInfos, subInfos...)
   184  		}
   185  		return paramInfos
   186  	default:
   187  		return []paramInfo{c.getParamInfo(t, name, goType)}
   188  	}
   189  }
   190  
   191  // getParamInfo collects information about a parameter. For example, if this
   192  // parameter is pointer-like, it will also store the element type for the
   193  // dereferenceable_or_null attribute.
   194  func (c *compilerContext) getParamInfo(t llvm.Type, name string, goType types.Type) paramInfo {
   195  	info := paramInfo{
   196  		llvmType: t,
   197  		name:     name,
   198  	}
   199  	if goType != nil {
   200  		switch underlying := goType.Underlying().(type) {
   201  		case *types.Pointer:
   202  			// Pointers in Go must either point to an object or be nil.
   203  			info.elemSize = c.targetData.TypeAllocSize(c.getLLVMType(underlying.Elem()))
   204  		case *types.Chan:
   205  			// Channels are implemented simply as a *runtime.channel.
   206  			info.elemSize = c.targetData.TypeAllocSize(c.getLLVMRuntimeType("channel"))
   207  		case *types.Map:
   208  			// Maps are similar to channels: they are implemented as a
   209  			// *runtime.hashmap.
   210  			info.elemSize = c.targetData.TypeAllocSize(c.getLLVMRuntimeType("hashmap"))
   211  		}
   212  	}
   213  	return info
   214  }
   215  
   216  // extractSubfield extracts a field from a struct, or returns null if this is
   217  // not a struct and thus no subfield can be obtained.
   218  func extractSubfield(t types.Type, field int) types.Type {
   219  	if t == nil {
   220  		return nil
   221  	}
   222  	switch t := t.Underlying().(type) {
   223  	case *types.Struct:
   224  		return t.Field(field).Type()
   225  	case *types.Interface, *types.Slice, *types.Basic, *types.Signature:
   226  		// These Go types are (sometimes) implemented as LLVM structs but can't
   227  		// really be split further up in Go (with the possible exception of
   228  		// complex numbers).
   229  		return nil
   230  	default:
   231  		// This should be unreachable.
   232  		panic("cannot split subfield: " + t.String())
   233  	}
   234  }
   235  
   236  // flattenAggregateTypeOffsets returns the offsets from the start of an object of
   237  // type t if this object were flattened like in flattenAggregate. Used together
   238  // with flattenAggregate to know the start indices of each value in the
   239  // non-flattened object.
   240  //
   241  // Note: this is an implementation detail, use expandFormalParamOffsets instead.
   242  func (c *compilerContext) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
   243  	switch t.TypeKind() {
   244  	case llvm.StructTypeKind:
   245  		var fields []uint64
   246  		for fieldIndex, field := range t.StructElementTypes() {
   247  			if c.targetData.TypeAllocSize(field) == 0 {
   248  				continue
   249  			}
   250  			suboffsets := c.flattenAggregateTypeOffsets(field)
   251  			offset := c.targetData.ElementOffset(t, fieldIndex)
   252  			for i := range suboffsets {
   253  				suboffsets[i] += offset
   254  			}
   255  			fields = append(fields, suboffsets...)
   256  		}
   257  		return fields
   258  	default:
   259  		return []uint64{0}
   260  	}
   261  }
   262  
   263  // flattenAggregate breaks down a struct into its elementary values for argument
   264  // passing. It is the value equivalent of flattenAggregateType
   265  func (b *builder) flattenAggregate(v llvm.Value) []llvm.Value {
   266  	switch v.Type().TypeKind() {
   267  	case llvm.StructTypeKind:
   268  		var fields []llvm.Value
   269  		for i, field := range v.Type().StructElementTypes() {
   270  			if b.targetData.TypeAllocSize(field) == 0 {
   271  				continue
   272  			}
   273  			subfield := b.CreateExtractValue(v, i, "")
   274  			subfields := b.flattenAggregate(subfield)
   275  			fields = append(fields, subfields...)
   276  		}
   277  		return fields
   278  	default:
   279  		return []llvm.Value{v}
   280  	}
   281  }
   282  
   283  // collapseFormalParam combines an aggregate object back into the original
   284  // value. This is used to join multiple LLVM parameters into a single Go value
   285  // in the function entry block.
   286  func (b *builder) collapseFormalParam(t llvm.Type, fields []llvm.Value) llvm.Value {
   287  	param, remaining := b.collapseFormalParamInternal(t, fields)
   288  	if len(remaining) != 0 {
   289  		panic("failed to expand back all fields")
   290  	}
   291  	return param
   292  }
   293  
   294  // collapseFormalParamInternal is an implementation detail of
   295  // collapseFormalParam: it works by recursing until there are no fields left.
   296  func (b *builder) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value) (llvm.Value, []llvm.Value) {
   297  	switch t.TypeKind() {
   298  	case llvm.StructTypeKind:
   299  		flattened := b.flattenAggregateType(t, "", nil)
   300  		if len(flattened) <= maxFieldsPerParam {
   301  			value := llvm.ConstNull(t)
   302  			for i, subtyp := range t.StructElementTypes() {
   303  				if b.targetData.TypeAllocSize(subtyp) == 0 {
   304  					continue
   305  				}
   306  				structField, remaining := b.collapseFormalParamInternal(subtyp, fields)
   307  				fields = remaining
   308  				value = b.CreateInsertValue(value, structField, i, "")
   309  			}
   310  			return value, fields
   311  		} else {
   312  			// this struct was not flattened
   313  			return fields[0], fields[1:]
   314  		}
   315  	default:
   316  		return fields[0], fields[1:]
   317  	}
   318  }