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 }