calls.go raw

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