diff --git a/compiler/calls.go b/compiler/calls.go index 257973320e..87ddf0c2aa 100644 --- a/compiler/calls.go +++ b/compiler/calls.go @@ -15,6 +15,15 @@ import ( // a struct contains more fields, it is passed as a struct without expanding. const maxFieldsPerParam = 3 +// The maximum number of scalar leaves an aggregate parameter may have before +// it is passed by pointer to a caller-owned copy instead of by value. Some +// backends (notably WebAssembly) flatten aggregate parameters into individual +// scalar parameters, and the WebAssembly JS embedding rejects function types +// with more than 1000 parameters. See paramNeedsSpill. +// With at most 16 scalars per parameter, a function needs over 60 such +// parameters to approach the 1000-parameter limit. +const maxLeavesPerParam = 16 + // paramInfo contains some information collected about a function parameter, // useful while declaring or defining a function. type paramInfo struct { @@ -105,7 +114,7 @@ func (b *builder) createInvoke(fnType llvm.Type, fn llvm.Value, args []llvm.Valu // Expand an argument type to a list that can be used in a function call // parameter list. func (c *compilerContext) expandFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo { - if c.isIndirectAggregate(t) { + if c.paramNeedsSpill(t) { return []paramInfo{{ llvmType: c.dataPtrType, name: name, @@ -138,7 +147,7 @@ func (c *compilerContext) storedParamType(t llvm.Type, exported bool) llvm.Type } func (c *compilerContext) isIndirectParam(t llvm.Type, exported bool) bool { - return !exported && c.isIndirectAggregate(t) + return !exported && c.paramNeedsSpill(t) } func (b *builder) appendStoredValueTypes(valueTypes []llvm.Type, values []ssa.Value, exported bool) []llvm.Type { @@ -298,6 +307,51 @@ func extractSubfield(t types.Type, field int) types.Type { } } +// countFlattenedLeaves returns the number of scalar values this type would be +// flattened into when passed by value. Unlike flattenAggregateType, it looks +// through arrays as well: LLVM's SelectionDAG (ComputeValueVTs) scalarizes +// both structs and arrays in by-value aggregate parameters. +func (c *compilerContext) countFlattenedLeaves(t llvm.Type) int { + switch t.TypeKind() { + case llvm.StructTypeKind: + count := 0 + for _, field := range t.StructElementTypes() { + if c.targetData.TypeAllocSize(field) == 0 { + continue + } + count += c.countFlattenedLeaves(field) + if count >= 1<<30 { + // Saturate instead of overflowing, like the array case below. + return 1 << 30 + } + } + return count + case llvm.ArrayTypeKind: + elemLeaves := c.countFlattenedLeaves(t.ElementType()) + length := t.ArrayLength() + if elemLeaves > 0 && length > (1<<30)/elemLeaves { + // Saturate instead of overflowing; this is already far above + // any spill threshold. + return 1 << 30 + } + return length * elemLeaves + default: + return 1 + } +} + +// paramNeedsSpill returns whether a parameter of this type must be passed by +// pointer to a caller-owned copy instead of by value in the Go-internal +// calling convention, to avoid creating functions with enormous numbers of +// parameters once the backend flattens the aggregate. +func (c *compilerContext) paramNeedsSpill(t llvm.Type) bool { + switch t.TypeKind() { + case llvm.StructTypeKind, llvm.ArrayTypeKind: + return c.countFlattenedLeaves(t) > maxLeavesPerParam + } + return false +} + // flattenAggregateTypeOffsets 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 diff --git a/compiler/calls_test.go b/compiler/calls_test.go new file mode 100644 index 0000000000..b99fd28751 --- /dev/null +++ b/compiler/calls_test.go @@ -0,0 +1,53 @@ +package compiler + +import ( + "testing" + + "tinygo.org/x/go-llvm" +) + +func TestParamNeedsSpill(t *testing.T) { + t.Parallel() + ctx := llvm.NewContext() + defer ctx.Dispose() + targetData := llvm.NewTargetData("e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20") + defer targetData.Dispose() + c := &compilerContext{ctx: ctx, targetData: targetData} + + i32 := ctx.Int32Type() + makeStruct := func(n int) llvm.Type { + fields := make([]llvm.Type, n) + for i := range fields { + fields[i] = i32 + } + return ctx.StructType(fields, false) + } + + for _, tc := range []struct { + name string + typ llvm.Type + leaves int + spill bool + }{ + {"i32", i32, 1, false}, + {"empty", makeStruct(0), 0, false}, + {"flat16", makeStruct(16), 16, false}, + {"flat17", makeStruct(17), 17, true}, + {"nested16", ctx.StructType([]llvm.Type{makeStruct(8), makeStruct(8)}, false), 16, false}, + {"nested17", ctx.StructType([]llvm.Type{makeStruct(8), makeStruct(9)}, false), 17, true}, + {"zeroSizeField", ctx.StructType([]llvm.Type{ctx.StructType(nil, false), i32}, false), 1, false}, + {"array16", llvm.ArrayType(i32, 16), 16, false}, + {"array17", llvm.ArrayType(i32, 17), 17, true}, + {"structWithArray", ctx.StructType([]llvm.Type{llvm.ArrayType(i32, 16), i32}, false), 17, true}, + {"hugeArray", llvm.ArrayType(makeStruct(4), 1<<29), 1 << 30, true}, + } { + t.Run(tc.name, func(t *testing.T) { + if leaves := c.countFlattenedLeaves(tc.typ); leaves != tc.leaves { + t.Errorf("countFlattenedLeaves = %d, want %d", leaves, tc.leaves) + } + if spill := c.paramNeedsSpill(tc.typ); spill != tc.spill { + t.Errorf("paramNeedsSpill = %v, want %v", spill, tc.spill) + } + }) + } +} diff --git a/compiler/compiler.go b/compiler/compiler.go index 51197bac34..80114bd962 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1444,8 +1444,13 @@ func (b *builder) createFunction() { for _, phi := range b.phis { block := phi.ssa.Block() for i, edge := range phi.ssa.Edges { - llvmVal := b.getCallArgument(edge, false) llvmBlock := b.blockInfo[block.Preds[i].Index].exit + // Materializing an indirect phi input may emit an allocation and a + // copy. Put those instructions in the corresponding predecessor, + // before its terminator, rather than at the builder's current end + // position after the function body has been emitted. + b.SetInsertPointBefore(llvmBlock.LastInstruction()) + llvmVal := b.getCallArgument(edge, false) phi.llvm.AddIncoming([]llvm.Value{llvmVal}, []llvm.BasicBlock{llvmBlock}) } } diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index d9188790cd..82f063f667 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/tinygo-org/tinygo/compileopts" + "github.com/tinygo-org/tinygo/compiler/llvmutil" "github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/loader" "tinygo.org/x/go-llvm" @@ -53,6 +54,7 @@ func TestCompiler(t *testing.T) { {"zeromap.go", "", ""}, {"generics.go", "", ""}, {"large.go", "", ""}, + {"paramspill.go", "", ""}, } if goMinor >= 20 { tests = append(tests, testCase{"go1.20.go", "", ""}) @@ -97,9 +99,18 @@ func TestCompiler(t *testing.T) { } // Optimize IR a little. + // Run instcombine without fixpoint verification: standalone textual + // instcombine fatally aborts when it needs more than one iteration + // (an LLVM 18+ testing aid); real pass pipelines run it with + // no-verify-fixpoint (see transform/optimizer.go). + passes := "instcombine" + if llvmutil.Version() < 18 { + // LLVM 17 doesn't have the no-verify-fixpoint flag. + passes = "instcombine" + } passOptions := llvm.NewPassBuilderOptions() defer passOptions.Dispose() - err = mod.RunPasses("instcombine", llvm.TargetMachine{}, passOptions) + err = mod.RunPasses(passes, llvm.TargetMachine{}, passOptions) if err != nil { t.Error(err) } @@ -324,6 +335,9 @@ func TestCompilerErrors(t *testing.T) { } expectedErrorsIdx++ } + for _, missing := range expectedErrors[expectedErrorsIdx:] { + t.Errorf("expected compiler error was not produced: %s", missing) + } } func TestAggregateValueCount(t *testing.T) { diff --git a/compiler/interface.go b/compiler/interface.go index 84f91cc448..558c38ddb9 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -1298,11 +1298,39 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFnType return wrapper } - // Get the expanded receiver type. + // Get the expanded receiver type. Exported methods keep their C ABI and + // therefore pass even large value receivers directly. + exported := c.getFunctionInfo(fn).exported + + // Interface invoke thunks use the Go internal calling convention, but + // exported methods keep their C ABI. The receiver is bridged below; a + // large aggregate result or parameter, however, is placed differently by + // the two conventions (indirect vs. direct), so such a method cannot be + // called through an interface. Report that instead of building a wrapper + // with a mismatched signature. + if exported { + if _, indirect := c.hasIndirectResult(fn.Signature); indirect { + c.addError(fn.Pos(), fmt.Sprintf("exported method %s with a large aggregate result cannot be called through an interface", fn.RelString(nil))) + return llvmFn + } + for param := range fn.Signature.Params().Variables() { + if c.paramNeedsSpill(c.getLLVMType(param.Type())) { + c.addError(fn.Pos(), fmt.Sprintf("exported method %s with a large aggregate parameter cannot be called through an interface", fn.RelString(nil))) + return llvmFn + } + } + } + receiverType := c.getLLVMType(fn.Signature.Recv().Type()) var expandedReceiverType []llvm.Type - receiverIndirect := c.isIndirectAggregate(receiverType) - for _, info := range c.expandFormalParamType(receiverType, "", nil) { + receiverIndirect := c.isIndirectParam(receiverType, exported) + var receiverInfos []paramInfo + if exported { + receiverInfos = c.expandDirectFormalParamType(receiverType, "", nil) + } else { + receiverInfos = c.expandFormalParamType(receiverType, "", nil) + } + for _, info := range receiverInfos { expandedReceiverType = append(expandedReceiverType, info.llvmType) } @@ -1317,7 +1345,7 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFnType // create wrapper function resultOffset := 0 - if _, indirect := c.hasIndirectResult(fn.Signature); indirect { + if _, indirect := c.hasIndirectResult(fn.Signature); indirect && !exported { resultOffset = 1 } paramTypes := append([]llvm.Type{}, llvmFnType.ParamTypes()[:resultOffset]...) diff --git a/compiler/testdata/errors.go b/compiler/testdata/errors.go index ae95b75234..092f2be2b3 100644 --- a/compiler/testdata/errors.go +++ b/compiler/testdata/errors.go @@ -105,3 +105,34 @@ func invalidreturn_chan_int() chan int // //go:wasmimport modulename invalidreturn_string func invalidreturn_string() string + +// Exported methods keep the C calling convention, but interface invoke thunks +// use the Go internal convention. When a method's result or a parameter is a +// large aggregate the two conventions place values differently, so calling +// such a method through an interface is not supported and must be reported +// instead of silently miscompiled. + +type exportedBigResult struct{} + +// ERROR: exported method (main.exportedBigResult).makeBig with a large aggregate result cannot be called through an interface +// +//export makeBigExported +func (exportedBigResult) makeBig() [1025]byte { + return [1025]byte{} +} + +type exportedBigParam struct{} + +// ERROR: exported method (main.exportedBigParam).takeBig with a large aggregate parameter cannot be called through an interface +// +//export takeBigExported +func (exportedBigParam) takeBig(a [17]int32) int32 { + return a[16] +} + +func useExportedMethodsThroughInterfaces() { + var m interface{ makeBig() [1025]byte } = exportedBigResult{} + m.makeBig() + var t interface{ takeBig([17]int32) int32 } = exportedBigParam{} + t.takeBig([17]int32{}) +} diff --git a/compiler/testdata/goroutine-wasm-asyncify.ll b/compiler/testdata/goroutine-wasm-asyncify.ll index 062ec1a423..934883d119 100644 --- a/compiler/testdata/goroutine-wasm-asyncify.ll +++ b/compiler/testdata/goroutine-wasm-asyncify.ll @@ -16,7 +16,7 @@ entry: ; Function Attrs: nounwind define hidden void @main.regularFunctionGoroutine(ptr %context) unnamed_addr #1 { entry: - call void @"internal/task.start"(i32 ptrtoint (ptr @"main.regularFunction$gowrapper" to i32), ptr nonnull inttoptr (i32 5 to ptr), i32 65536, ptr undef) #11 + call void @"internal/task.start"(i32 ptrtoint (ptr @"main.regularFunction$gowrapper" to i32), ptr nonnull inttoptr (i32 5 to ptr), i32 131072, ptr undef) #11 ret void } @@ -38,7 +38,7 @@ declare void @"internal/task.start"(i32, ptr, i32, ptr) #0 ; Function Attrs: nounwind define hidden void @main.inlineFunctionGoroutine(ptr %context) unnamed_addr #1 { entry: - call void @"internal/task.start"(i32 ptrtoint (ptr @"main.inlineFunctionGoroutine$1$gowrapper" to i32), ptr nonnull inttoptr (i32 5 to ptr), i32 65536, ptr undef) #11 + call void @"internal/task.start"(i32 ptrtoint (ptr @"main.inlineFunctionGoroutine$1$gowrapper" to i32), ptr nonnull inttoptr (i32 5 to ptr), i32 131072, ptr undef) #11 ret void } @@ -71,7 +71,7 @@ entry: store i32 5, ptr %0, align 4 %1 = getelementptr inbounds nuw i8, ptr %0, i32 4 store ptr %n, ptr %1, align 4 - call void @"internal/task.start"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr nonnull %0, i32 65536, ptr undef) #11 + call void @"internal/task.start"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr nonnull %0, i32 131072, ptr undef) #11 %2 = load i32, ptr %n, align 4 call void @runtime.printlock(ptr undef) #11 call void @runtime.printint32(i32 %2, ptr undef) #11 @@ -117,7 +117,7 @@ entry: store ptr %fn.context, ptr %1, align 4 %2 = getelementptr inbounds nuw i8, ptr %0, i32 8 store ptr %fn.funcptr, ptr %2, align 4 - call void @"internal/task.start"(i32 ptrtoint (ptr @main.funcGoroutine.gowrapper to i32), ptr nonnull %0, i32 65536, ptr undef) #11 + call void @"internal/task.start"(i32 ptrtoint (ptr @main.funcGoroutine.gowrapper to i32), ptr nonnull %0, i32 131072, ptr undef) #11 ret void } @@ -176,7 +176,7 @@ entry: store i32 4, ptr %2, align 4 %3 = getelementptr inbounds nuw i8, ptr %0, i32 12 store ptr %itf.typecode, ptr %3, align 4 - call void @"internal/task.start"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr nonnull %0, i32 65536, ptr undef) #11 + call void @"internal/task.start"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr nonnull %0, i32 131072, ptr undef) #11 ret void } diff --git a/compiler/testdata/large.ll b/compiler/testdata/large.ll index cc1f8556b4..9fdaaccf26 100644 --- a/compiler/testdata/large.ll +++ b/compiler/testdata/large.ll @@ -199,7 +199,7 @@ entry: %go.param = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 call void @runtime.trackPointer(ptr nonnull %go.param, ptr nonnull %stackalloc, ptr undef) #9 call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %go.param, ptr noundef nonnull align 1 dereferenceable(1025) %value, i32 1025, i1 false) - call void @"internal/task.start"(i32 ptrtoint (ptr @"main.readLargeValue$gowrapper" to i32), ptr nonnull %go.param, i32 65536, ptr undef) #9 + call void @"internal/task.start"(i32 ptrtoint (ptr @"main.readLargeValue$gowrapper" to i32), ptr nonnull %go.param, i32 131072, ptr undef) #9 ret void } diff --git a/compiler/testdata/paramspill.go b/compiler/testdata/paramspill.go new file mode 100644 index 0000000000..4b6beee283 --- /dev/null +++ b/compiler/testdata/paramspill.go @@ -0,0 +1,130 @@ +package main + +// Aggregate parameters with more than 16 flattened scalar leaves are passed +// by pointer to a caller-owned copy in the Go ABI (see paramNeedsSpill), so +// backends that flatten aggregates (wasm) don't create functions with +// enormous parameter lists. Exported (C ABI) functions are not affected. + +type big struct { // 17 leaves: passed by pointer + a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q int +} + +type edge struct { // 16 leaves: still passed by value + a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p int +} + +type withArray struct { // 1 + 16 = 17 leaves (arrays count): passed by pointer + tag int + buf [16]int32 +} + +func (b big) sum() int { + return b.a + b.q +} + +type summer interface { + sum() int +} + +var sink int + +func takeBig(b big) int { + return b.q +} + +func takeEdge(e edge) int { + return e.p +} + +func takeArray(a [17]int32) int32 { + return a[16] +} + +func takeWithArray(w withArray) int32 { + return w.buf[0] +} + +func takeBigPhi(cond bool, x, y big) int { + var selected big + if cond { + selected = x + } else { + selected = y + } + return selected.q +} + +// takeLoadedBigPhi produces a real SSA phi of a spilled aggregate: selected.q +// above takes a field address, which stops go/ssa from lifting the variable, +// but v here is only ever used whole, so it lifts into a phi. Neither incoming +// value is in indirect storage already (one is a constant, the other a plain +// load), so resolving the phi must materialize storage for each edge inside +// that edge's predecessor block, before its terminator. +func takeLoadedBigPhi(cond bool, p *big) int { + v := big{} + if cond { + v = *p + } + return takeBig(v) +} + +// loopBigPhi is the back-edge variant: the load in the loop body feeds the +// header phi, so its materialization must land before the body's branch. +func loopBigPhi(n int, p *big) int { + v := big{} + for i := 0; i < n; i++ { + v = *p + } + return takeBig(v) +} + +//export takeBigC +func takeBigC(b big) int { + return b.a +} + +func spawnBig(b big) { + sink = b.a +} + +// pickTakeBig hides the callee behind a function value: //go:noinline keeps +// the SSA builder from resolving f(b) below to a static callee, so the call +// goes through the func-value decode path (extract the code pointer, nil +// check, indirect call) with the spilled parameter. +// +//go:noinline +func pickTakeBig() func(big) int { + return takeBig +} + +func callEverything(b big, e edge, a [17]int32, w withArray, s summer) int { + sum := takeBig(b) + sum += takeEdge(e) + sum += int(takeArray(a)) + sum += int(takeWithArray(w)) + sum += takeBigPhi(sum != 0, b, big{}) + sum += takeBigC(b) + f := pickTakeBig() + sum += f(b) + sum += s.sum() + // Note: the lowering of this `go` statement depends on the default + // scheduler of the wasm test target (asyncify). + go spawnBig(b) + return sum +} + +func makeInterface(b big) summer { + return b +} + +// Exported method: the C ABI is used for the receiver, also in the interface +// invoke wrapper, so the >16-leaf receiver is not spilled. +// +//export sumWithArrayC +func (w withArray) sum() int { + return w.tag +} + +func makeInterfaceWithArray(w withArray) summer { + return w +} diff --git a/compiler/testdata/paramspill.ll b/compiler/testdata/paramspill.ll new file mode 100644 index 0000000000..fdeb39f401 --- /dev/null +++ b/compiler/testdata/paramspill.ll @@ -0,0 +1,1174 @@ +; ModuleID = 'paramspill.go' +source_filename = "paramspill.go" +target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20" +target triple = "wasm32-unknown-wasi" + +%runtime.structField = type { ptr, ptr } +%main.edge = type { i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32 } +%main.big = type { i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32 } +%runtime._interface = type { ptr, ptr } + +@main.sink = hidden global i32 0, align 4 +@"reflect/types.signature:main.sum:func:{}{basic:int}" = linkonce_odr constant i8 0, align 1 +@"reflect/types.type:named:main.big" = linkonce_odr constant { ptr, i8, i16, ptr, ptr, ptr, { i32, [1 x ptr] }, [9 x i8] } { ptr @"named:main.big$methodset", i8 122, i16 -32768, ptr getelementptr ({ ptr, i8, i16, ptr, { i32, [1 x ptr] } }, ptr @"reflect/types.type:pointer:named:main.big", i32 0, i32 1), ptr @"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}", ptr @"reflect/types.type.pkgpath:main", { i32, [1 x ptr] } { i32 1, [1 x ptr] [ptr @"reflect/types.signature:main.sum:func:{}{basic:int}"] }, [9 x i8] c"main.big\00" }, align 4 +@"reflect/types.type.pkgpath:main" = linkonce_odr unnamed_addr constant [5 x i8] c"main\00", align 1 +@"reflect/types.type:pointer:named:main.big" = linkonce_odr constant { ptr, i8, i16, ptr, { i32, [1 x ptr] } } { ptr @"pointer:named:main.big$methodset", i8 -43, i16 -32768, ptr getelementptr ({ ptr, i8, i16, ptr, ptr, ptr, { i32, [1 x ptr] }, [9 x i8] }, ptr @"reflect/types.type:named:main.big", i32 0, i32 1), { i32, [1 x ptr] } { i32 1, [1 x ptr] [ptr @"reflect/types.signature:main.sum:func:{}{basic:int}"] } }, align 4 +@"main.$methods.sum:func:{}{basic:int}" = linkonce_odr constant i8 0, align 1 +@"main$string" = internal unnamed_addr constant [8 x i8] c"main.big", align 1 +@"main$string.1" = internal unnamed_addr constant [3 x i8] c"sum", align 1 +@"pointer:named:main.big$methodset" = linkonce_odr unnamed_addr constant { i32, [1 x ptr], { ptr } } { i32 1, [1 x ptr] [ptr @"main.$methods.sum:func:{}{basic:int}"], { ptr } { ptr @"(*main.big).sum" } } +@"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}" = linkonce_odr constant { i8, i16, ptr, ptr, i32, i16, [17 x %runtime.structField] } { i8 90, i16 0, ptr @"reflect/types.type:pointer:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}", ptr @"reflect/types.type.pkgpath:main", i32 68, i16 17, [17 x %runtime.structField] [%runtime.structField { ptr @"reflect/types.type:basic:int", ptr @"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.a" }, %runtime.structField { ptr @"reflect/types.type:basic:int", ptr @"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.b" }, %runtime.structField { ptr @"reflect/types.type:basic:int", ptr @"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.c" }, %runtime.structField { ptr @"reflect/types.type:basic:int", ptr @"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.d" }, %runtime.structField { ptr @"reflect/types.type:basic:int", ptr @"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.e" }, %runtime.structField { ptr @"reflect/types.type:basic:int", ptr @"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.f" }, %runtime.structField { ptr @"reflect/types.type:basic:int", ptr @"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.g" }, %runtime.structField { ptr @"reflect/types.type:basic:int", ptr @"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.h" }, %runtime.structField { ptr @"reflect/types.type:basic:int", ptr @"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.i" }, %runtime.structField { ptr @"reflect/types.type:basic:int", ptr @"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.j" }, %runtime.structField { ptr @"reflect/types.type:basic:int", ptr @"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.k" }, %runtime.structField { ptr @"reflect/types.type:basic:int", ptr @"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.l" }, %runtime.structField { ptr @"reflect/types.type:basic:int", ptr @"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.m" }, %runtime.structField { ptr @"reflect/types.type:basic:int", ptr @"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.n" }, %runtime.structField { ptr @"reflect/types.type:basic:int", ptr @"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.o" }, %runtime.structField { ptr @"reflect/types.type:basic:int", ptr @"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.p" }, %runtime.structField { ptr @"reflect/types.type:basic:int", ptr @"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.q" }] }, align 4 +@"reflect/types.type:pointer:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}" }, align 4 +@"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.a" = internal unnamed_addr constant [4 x i8] c"\00\00a\00", align 1 +@"reflect/types.type:basic:int" = linkonce_odr constant { i8, ptr } { i8 -62, ptr @"reflect/types.type:pointer:basic:int" }, align 4 +@"reflect/types.type:pointer:basic:int" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:basic:int" }, align 4 +@"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.b" = internal unnamed_addr constant [4 x i8] c"\00\04b\00", align 1 +@"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.c" = internal unnamed_addr constant [4 x i8] c"\00\08c\00", align 1 +@"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.d" = internal unnamed_addr constant [4 x i8] c"\00\0Cd\00", align 1 +@"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.e" = internal unnamed_addr constant [4 x i8] c"\00\10e\00", align 1 +@"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.f" = internal unnamed_addr constant [4 x i8] c"\00\14f\00", align 1 +@"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.g" = internal unnamed_addr constant [4 x i8] c"\00\18g\00", align 1 +@"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.h" = internal unnamed_addr constant [4 x i8] c"\00\1Ch\00", align 1 +@"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.i" = internal unnamed_addr constant [4 x i8] c"\00 i\00", align 1 +@"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.j" = internal unnamed_addr constant [4 x i8] c"\00$j\00", align 1 +@"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.k" = internal unnamed_addr constant [4 x i8] c"\00(k\00", align 1 +@"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.l" = internal unnamed_addr constant [4 x i8] c"\00,l\00", align 1 +@"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.m" = internal unnamed_addr constant [4 x i8] c"\000m\00", align 1 +@"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.n" = internal unnamed_addr constant [4 x i8] c"\004n\00", align 1 +@"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.o" = internal unnamed_addr constant [4 x i8] c"\008o\00", align 1 +@"reflect/types.type:struct:{a:basic:int,b:basic:int,c:basic:int,d:basic:int,e:basic:int,f:basic:int,g:basic:int,h:basic:int,i:basic:int,j:basic:int,k:basic:int,l:basic:int,m:basic:int,n:basic:int,o:basic:int,p:basic:int,q:basic:int}.p" = internal unnamed_addr constant [4 x i8] c"\00