From 3041c6050a65c3bfa35de6f41654375936b46466 Mon Sep 17 00:00:00 2001 From: Evan Wies Date: Tue, 9 Jun 2026 16:33:43 -0400 Subject: [PATCH 1/7] compiler: add spill predicate for large aggregate parameters Signed-off-by: Evan Wies --- compiler/calls.go | 54 ++++++++++++++++++++++++++++++++++++++++++ compiler/calls_test.go | 53 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 compiler/calls_test.go diff --git a/compiler/calls.go b/compiler/calls.go index 257973320e..a53cabdc98 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 { @@ -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) + } + }) + } +} From e9686af146c94f3d535e3ba3191eb7949f889d6f Mon Sep 17 00:00:00 2001 From: Evan Wies Date: Tue, 9 Jun 2026 17:28:13 -0400 Subject: [PATCH 2/7] compiler: pass large aggregate parameters by pointer in the Go ABI LLVM's WebAssembly backend flattens aggregate parameters into individual scalar parameters, and the WebAssembly JS embedding rejects function types with more than 1000 parameters. Large value structs (for example lipgloss.Style with ~95 scalar leaves, or list.Model which contains ~40 of them) therefore produced functions with thousands of wasm parameters that browsers refuse to instantiate: argument count of Type ... is too big 3730 maximum 1000 With this change, parameters with more than 16 flattened scalar leaves (arrays counted per element) are passed by pointer to a caller-owned copy in the Go-internal calling convention: the caller stores the value into a temporary alloca and passes the pointer, and the callee loads it once at function entry. The pointer is non-null, read-only, properly aligned, and does not escape, and is annotated accordingly. Exported and external functions (//export, cgo, C ABI) deliberately keep the previous behavior, selected via a new abiKind threaded through the shared parameter expansion helpers. Deferred calls inherit the new convention automatically because defer stores unexpanded values and re-issues the call through createCall at rundefers time. Signed-off-by: Evan Wies --- compiler/calls.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/calls.go b/compiler/calls.go index a53cabdc98..87ddf0c2aa 100644 --- a/compiler/calls.go +++ b/compiler/calls.go @@ -114,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, @@ -147,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 { From 8ecc01ac9721a3e37dd7403e9e827b5a0a029975 Mon Sep 17 00:00:00 2001 From: Evan Wies Date: Tue, 9 Jun 2026 19:49:51 -0400 Subject: [PATCH 3/7] compiler: spill large value receivers in interface invoke wrappers The invoke wrapper unpacks the receiver from the interface box and calls the real method, so a >16-leaf value receiver must be re-spilled to match the wrapped signature. Pass the raw receiver to createCallABI with the ABI of the wrapped function: Go-ABI methods get the spill, while exported (C ABI) methods keep by-value receiver expansion, also in their wrapper. Signed-off-by: Evan Wies --- compiler/compiler_test.go | 3 +++ compiler/interface.go | 36 ++++++++++++++++++++++++++++++++---- compiler/testdata/errors.go | 31 +++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 4 deletions(-) diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index d9188790cd..762922dfc5 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -324,6 +324,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{}) +} From 706e656e45d9222df7dd8be3f3ae96c7bf349795 Mon Sep 17 00:00:00 2001 From: Evan Wies Date: Tue, 9 Jun 2026 21:27:22 -0400 Subject: [PATCH 4/7] compiler: add golden IR test for large parameter spilling Pins the new convention: the 16/17-leaf boundary, array leaf counting, the unchanged C ABI of exported functions (including methods invoked through an interface), caller-side spill allocas, the heap spill for go statements, and the interface invoke wrapper. This also changes the golden test harness to run instcombine with no-verify-fixpoint, affecting all golden tests: standalone textual instcombine fatally aborts when it needs more than one iteration (an LLVM 18+ testing aid), which the interface-boxing code in this test triggers. Real pass pipelines (see transform/optimizer.go) already run instcombine with no-verify-fixpoint, falling back to plain instcombine before LLVM 18 where the flag does not exist; the harness mirrors that. All other golden files produce byte-identical output. Signed-off-by: Evan Wies --- compiler/compiler_test.go | 13 +- compiler/testdata/paramspill.go | 95 ++++ compiler/testdata/paramspill.ll | 848 ++++++++++++++++++++++++++++++++ 3 files changed, 955 insertions(+), 1 deletion(-) create mode 100644 compiler/testdata/paramspill.go create mode 100644 compiler/testdata/paramspill.ll diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 762922dfc5..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) } diff --git a/compiler/testdata/paramspill.go b/compiler/testdata/paramspill.go new file mode 100644 index 0000000000..aa8cf530cb --- /dev/null +++ b/compiler/testdata/paramspill.go @@ -0,0 +1,95 @@ +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] +} + +//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 += 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..b0ac1ecd95 --- /dev/null +++ b/compiler/testdata/paramspill.ll @@ -0,0 +1,848 @@ +; 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