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