255 строки
8,2 КиБ
Go
255 строки
8,2 КиБ
Go
package compiler
|
|
|
|
import (
|
|
"fmt"
|
|
"golang.org/x/tools/go/ssa"
|
|
"tinygo.org/x/go-llvm"
|
|
)
|
|
|
|
// For a description of the calling convention in prose, see:
|
|
// https://tinygo.org/compiler-internals/calling-convention/
|
|
|
|
// The maximum number of arguments that can be expanded from a single struct. If
|
|
// a struct contains more fields, it is passed as a struct without expanding.
|
|
const MaxFieldsPerParam = 3
|
|
|
|
// Shortcut: create a call to runtime.<fnName> with the given arguments.
|
|
func (c *Compiler) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value {
|
|
runtimePkg := c.ir.Program.ImportedPackage("runtime")
|
|
member := runtimePkg.Members[fnName]
|
|
if member == nil {
|
|
panic("trying to call runtime." + fnName)
|
|
}
|
|
fn := c.ir.GetFunction(member.(*ssa.Function))
|
|
if fn.LLVMFn.IsNil() {
|
|
panic(fmt.Errorf("function %s does not appear in LLVM IR", fnName))
|
|
}
|
|
if !fn.IsExported() {
|
|
args = append(args, llvm.Undef(c.i8ptrType)) // unused context parameter
|
|
args = append(args, llvm.ConstPointerNull(c.i8ptrType)) // coroutine handle
|
|
}
|
|
return c.createCall(fn.LLVMFn, args, name)
|
|
}
|
|
|
|
// createCall creates a new call to runtime.<fnName> with the given arguments.
|
|
func (b *builder) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value {
|
|
fullName := "runtime." + fnName
|
|
fn := b.mod.NamedFunction(fullName)
|
|
if fn.IsNil() {
|
|
panic("trying to call non-existent function: " + fullName)
|
|
}
|
|
args = append(args, llvm.Undef(b.i8ptrType)) // unused context parameter
|
|
args = append(args, llvm.ConstPointerNull(b.i8ptrType)) // coroutine handle
|
|
return b.createCall(fn, args, name)
|
|
}
|
|
|
|
// Create a call to the given function with the arguments possibly expanded.
|
|
func (c *Compiler) createCall(fn llvm.Value, args []llvm.Value, name string) llvm.Value {
|
|
expanded := make([]llvm.Value, 0, len(args))
|
|
for _, arg := range args {
|
|
fragments := c.expandFormalParam(arg)
|
|
expanded = append(expanded, fragments...)
|
|
}
|
|
return c.builder.CreateCall(fn, expanded, name)
|
|
}
|
|
|
|
// createCall creates a call to the given function with the arguments possibly
|
|
// expanded.
|
|
func (b *builder) createCall(fn llvm.Value, args []llvm.Value, name string) llvm.Value {
|
|
expanded := make([]llvm.Value, 0, len(args))
|
|
for _, arg := range args {
|
|
fragments := b.expandFormalParam(arg)
|
|
expanded = append(expanded, fragments...)
|
|
}
|
|
return b.CreateCall(fn, expanded, name)
|
|
}
|
|
|
|
// Expand an argument type to a list that can be used in a function call
|
|
// parameter list.
|
|
func expandFormalParamType(t llvm.Type) []llvm.Type {
|
|
switch t.TypeKind() {
|
|
case llvm.StructTypeKind:
|
|
fields := flattenAggregateType(t)
|
|
if len(fields) <= MaxFieldsPerParam {
|
|
return fields
|
|
} else {
|
|
// failed to lower
|
|
return []llvm.Type{t}
|
|
}
|
|
default:
|
|
// TODO: split small arrays
|
|
return []llvm.Type{t}
|
|
}
|
|
}
|
|
|
|
// expandFormalParamOffsets returns a list of offsets from the start of an
|
|
// object of type t after it would have been split up by expandFormalParam. This
|
|
// is useful for debug information, where it is necessary to know the offset
|
|
// from the start of the combined object.
|
|
func (b *builder) expandFormalParamOffsets(t llvm.Type) []uint64 {
|
|
switch t.TypeKind() {
|
|
case llvm.StructTypeKind:
|
|
fields := b.flattenAggregateTypeOffsets(t)
|
|
if len(fields) <= MaxFieldsPerParam {
|
|
return fields
|
|
} else {
|
|
// failed to lower
|
|
return []uint64{0}
|
|
}
|
|
default:
|
|
// TODO: split small arrays
|
|
return []uint64{0}
|
|
}
|
|
}
|
|
|
|
// Equivalent of expandFormalParamType for parameter values.
|
|
func (c *Compiler) expandFormalParam(v llvm.Value) []llvm.Value {
|
|
switch v.Type().TypeKind() {
|
|
case llvm.StructTypeKind:
|
|
fieldTypes := flattenAggregateType(v.Type())
|
|
if len(fieldTypes) <= MaxFieldsPerParam {
|
|
fields := c.flattenAggregate(v)
|
|
if len(fields) != len(fieldTypes) {
|
|
panic("type and value param lowering don't match")
|
|
}
|
|
return fields
|
|
} else {
|
|
// failed to lower
|
|
return []llvm.Value{v}
|
|
}
|
|
default:
|
|
// TODO: split small arrays
|
|
return []llvm.Value{v}
|
|
}
|
|
}
|
|
|
|
// expandFormalParam splits a formal param value into pieces, so it can be
|
|
// passed directly as part of a function call. For example, it splits up small
|
|
// structs into individual fields. It is the equivalent of expandFormalParamType
|
|
// for parameter values.
|
|
func (b *builder) expandFormalParam(v llvm.Value) []llvm.Value {
|
|
switch v.Type().TypeKind() {
|
|
case llvm.StructTypeKind:
|
|
fieldTypes := flattenAggregateType(v.Type())
|
|
if len(fieldTypes) <= MaxFieldsPerParam {
|
|
fields := b.flattenAggregate(v)
|
|
if len(fields) != len(fieldTypes) {
|
|
panic("type and value param lowering don't match")
|
|
}
|
|
return fields
|
|
} else {
|
|
// failed to lower
|
|
return []llvm.Value{v}
|
|
}
|
|
default:
|
|
// TODO: split small arrays
|
|
return []llvm.Value{v}
|
|
}
|
|
}
|
|
|
|
// Try to flatten a struct type to a list of types. Returns a 1-element slice
|
|
// with the passed in type if this is not possible.
|
|
func flattenAggregateType(t llvm.Type) []llvm.Type {
|
|
switch t.TypeKind() {
|
|
case llvm.StructTypeKind:
|
|
fields := make([]llvm.Type, 0, t.StructElementTypesCount())
|
|
for _, subfield := range t.StructElementTypes() {
|
|
subfields := flattenAggregateType(subfield)
|
|
fields = append(fields, subfields...)
|
|
}
|
|
return fields
|
|
default:
|
|
return []llvm.Type{t}
|
|
}
|
|
}
|
|
|
|
// flattenAggregateTypeOffset returns the offsets from the start of an object of
|
|
// type t if this object were flattened like in flattenAggregate. Used together
|
|
// with flattenAggregate to know the start indices of each value in the
|
|
// non-flattened object.
|
|
//
|
|
// Note: this is an implementation detail, use expandFormalParamOffsets instead.
|
|
func (c *compilerContext) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
|
|
switch t.TypeKind() {
|
|
case llvm.StructTypeKind:
|
|
fields := make([]uint64, 0, t.StructElementTypesCount())
|
|
for fieldIndex, field := range t.StructElementTypes() {
|
|
suboffsets := c.flattenAggregateTypeOffsets(field)
|
|
offset := c.targetData.ElementOffset(t, fieldIndex)
|
|
for i := range suboffsets {
|
|
suboffsets[i] += offset
|
|
}
|
|
fields = append(fields, suboffsets...)
|
|
}
|
|
return fields
|
|
default:
|
|
return []uint64{0}
|
|
}
|
|
}
|
|
|
|
// Break down a struct into its elementary types for argument passing. The value
|
|
// equivalent of flattenAggregateType
|
|
func (c *Compiler) flattenAggregate(v llvm.Value) []llvm.Value {
|
|
switch v.Type().TypeKind() {
|
|
case llvm.StructTypeKind:
|
|
fields := make([]llvm.Value, 0, v.Type().StructElementTypesCount())
|
|
for i := range v.Type().StructElementTypes() {
|
|
subfield := c.builder.CreateExtractValue(v, i, "")
|
|
subfields := c.flattenAggregate(subfield)
|
|
fields = append(fields, subfields...)
|
|
}
|
|
return fields
|
|
default:
|
|
return []llvm.Value{v}
|
|
}
|
|
}
|
|
|
|
// flattenAggregate breaks down a struct into its elementary values for argument
|
|
// passing. It is the value equivalent of flattenAggregateType
|
|
func (b *builder) flattenAggregate(v llvm.Value) []llvm.Value {
|
|
switch v.Type().TypeKind() {
|
|
case llvm.StructTypeKind:
|
|
fields := make([]llvm.Value, 0, v.Type().StructElementTypesCount())
|
|
for i := range v.Type().StructElementTypes() {
|
|
subfield := b.CreateExtractValue(v, i, "")
|
|
subfields := b.flattenAggregate(subfield)
|
|
fields = append(fields, subfields...)
|
|
}
|
|
return fields
|
|
default:
|
|
return []llvm.Value{v}
|
|
}
|
|
}
|
|
|
|
// collapseFormalParam combines an aggregate object back into the original
|
|
// value. This is used to join multiple LLVM parameters into a single Go value
|
|
// in the function entry block.
|
|
func (b *builder) collapseFormalParam(t llvm.Type, fields []llvm.Value) llvm.Value {
|
|
param, remaining := b.collapseFormalParamInternal(t, fields)
|
|
if len(remaining) != 0 {
|
|
panic("failed to expand back all fields")
|
|
}
|
|
return param
|
|
}
|
|
|
|
// collapseFormalParamInternal is an implementation detail of
|
|
// collapseFormalParam: it works by recursing until there are no fields left.
|
|
func (b *builder) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value) (llvm.Value, []llvm.Value) {
|
|
switch t.TypeKind() {
|
|
case llvm.StructTypeKind:
|
|
if len(flattenAggregateType(t)) <= MaxFieldsPerParam {
|
|
value := llvm.ConstNull(t)
|
|
for i, subtyp := range t.StructElementTypes() {
|
|
structField, remaining := b.collapseFormalParamInternal(subtyp, fields)
|
|
fields = remaining
|
|
value = b.CreateInsertValue(value, structField, i, "")
|
|
}
|
|
return value, fields
|
|
} else {
|
|
// this struct was not flattened
|
|
return fields[0], fields[1:]
|
|
}
|
|
default:
|
|
return fields[0], fields[1:]
|
|
}
|
|
}
|