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