From 9e7d89d4d53d38054a06ade93a7856bb5a6369c4 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Tue, 14 Jul 2026 06:22:02 -0700 Subject: [PATCH] compiler: pass large aggregates by pointer LLVM ComputeValueVTs recursively expands arrays and structs into one value type per scalar leaf. SelectionDAG call lowering allocates data structures proportional to this count, which makes very large values exhaust memory or crash LLVM. Count scalar leaves and use pointers for internal parameters and results when the count exceeds 1024. A result pointer is the first parameter, and aggregate parameters point to read-only memory. Exported function types are unchanged. Keep these SSA values in memory and copy them with memcpy when needed. Handle calls, interfaces, maps, channels, selects, defers, goroutines, phis, and multiple results. Update the expected compiler IR and re-enable the native compress/flate tests. --- GNUmakefile | 6 +- compiler/calls.go | 43 +++ compiler/channel.go | 16 +- compiler/compiler.go | 231 ++++++++++++++-- compiler/compiler_test.go | 83 ++++++ compiler/defer.go | 26 +- compiler/func.go | 80 +++++- compiler/goroutine.go | 14 +- compiler/interface.go | 49 +++- compiler/map.go | 18 +- compiler/symbol.go | 28 +- compiler/testdata/large-optimized.go | 20 ++ compiler/testdata/large.ll | 389 +++++++++++++++------------ transform/interface-lowering.go | 24 +- 14 files changed, 793 insertions(+), 234 deletions(-) create mode 100644 compiler/testdata/large-optimized.go diff --git a/GNUmakefile b/GNUmakefile index fb300a566..321eaca77 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -387,9 +387,7 @@ TEST_PACKAGES_FAST = \ # archive/zip requires os.ReadAt, which is not yet supported on windows # bytes requires mmap -# compress/flate appears to hang on wasi; on all targets Go 1.27's new -# compress/flate.indexTokens returns a ~262KB struct by value which crashes -# LLVM's X86 instruction selector during LTO codegen +# compress/flate appears to hang on wasi # crypto/aes needs reflect.Type.Method(), not yet implemented # crypto/des fails on wasi, needs panic()/recover() # crypto/hmac fails on wasi, it exits with a "slice out of range" panic @@ -411,6 +409,7 @@ TEST_PACKAGES_FAST = \ # Additional standard library packages that pass tests on individual platforms TEST_PACKAGES_LINUX := \ archive/zip \ + compress/flate \ context \ crypto/aes \ crypto/des \ @@ -438,6 +437,7 @@ TEST_PACKAGES_DARWIN := $(TEST_PACKAGES_LINUX) # os/user requires t.Skip() support TEST_PACKAGES_WINDOWS := \ + compress/flate \ crypto/des \ crypto/hmac \ image \ diff --git a/compiler/calls.go b/compiler/calls.go index fb8238062..257973320 100644 --- a/compiler/calls.go +++ b/compiler/calls.go @@ -35,6 +35,9 @@ const ( // Whether this is a readonly parameter (for example, a string pointer). paramIsReadonly + + // Whether this parameter is passed through backing storage. + paramIsIndirect ) // createRuntimeCallCommon creates a runtime call. Use createRuntimeCall or @@ -102,6 +105,14 @@ 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) { + return []paramInfo{{ + llvmType: c.dataPtrType, + name: name, + elemSize: c.targetData.TypeAllocSize(t), + flags: paramIsGoParam | paramIsReadonly | paramIsIndirect, + }} + } return c.expandDirectFormalParamType(t, name, goType) } @@ -119,6 +130,38 @@ func (c *compilerContext) expandDirectFormalParamType(t llvm.Type, name string, return []paramInfo{c.getParamInfo(t, name, goType)} } +func (c *compilerContext) storedParamType(t llvm.Type, exported bool) llvm.Type { + if c.isIndirectParam(t, exported) { + return c.dataPtrType + } + return t +} + +func (c *compilerContext) isIndirectParam(t llvm.Type, exported bool) bool { + return !exported && c.isIndirectAggregate(t) +} + +func (b *builder) appendStoredValueTypes(valueTypes []llvm.Type, values []ssa.Value, exported bool) []llvm.Type { + for _, value := range values { + valueTypes = append(valueTypes, b.storedParamType(b.getLLVMType(value.Type()), exported)) + } + return valueTypes +} + +func (b *builder) appendStoredParamTypes(valueTypes []llvm.Type, params []*types.Var, exported bool) []llvm.Type { + for _, param := range params { + valueTypes = append(valueTypes, b.storedParamType(b.getLLVMType(param.Type()), exported)) + } + return valueTypes +} + +func (b *builder) prependIndirectResult(sig *types.Signature, exported bool, params []llvm.Value, name string) []llvm.Value { + if resultType, indirect := b.hasIndirectResult(sig); !exported && indirect { + return append([]llvm.Value{b.createIndirectStorage(resultType, name)}, params...) + } + return params +} + // 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 diff --git a/compiler/channel.go b/compiler/channel.go index 444c1f2b1..a562e97e3 100644 --- a/compiler/channel.go +++ b/compiler/channel.go @@ -34,11 +34,11 @@ func (b *builder) createChanSend(instr *ssa.Send) { // store value-to-send valueType := b.getLLVMType(instr.X.Type()) isZeroSize := b.targetData.TypeAllocSize(valueType) == 0 - var valueAlloca, valueAllocaSize llvm.Value + var storage valueStorage if isZeroSize { - valueAlloca = llvm.ConstNull(b.dataPtrType) + storage.ptr = llvm.ConstNull(b.dataPtrType) } else { - valueAlloca, valueAllocaSize = b.getValueStorage(instr.X, "chan.value") + storage = b.getValueStorage(instr.X, "chan.value") } // Allocate buffer for the channel operation. @@ -46,15 +46,13 @@ func (b *builder) createChanSend(instr *ssa.Send) { channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op") // Do the send. - b.createRuntimeInvoke("chanSend", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "") + b.createRuntimeInvoke("chanSend", []llvm.Value{ch, storage.ptr, channelOpAlloca}, "") // End the lifetime of the allocas. // This also works around a bug in CoroSplit, at least in LLVM 8: // https://bugs.llvm.org/show_bug.cgi?id=41742 b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize) - if !isZeroSize { - b.emitLifetimeEnd(valueAlloca, valueAllocaSize) - } + b.endValueStorage(storage) } // createChanRecv emits a pseudo chan receive operation. It is lowered to the @@ -63,6 +61,7 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value { valueType := b.getLLVMType(unop.X.Type().Underlying().(*types.Chan).Elem()) ch := b.getValue(unop.X, getPos(unop)) + // Allocate memory to receive into. result := b.createRuntimeValueResult(valueType, unop.CommaOk, true, "chan") // Allocate buffer for the channel operation. @@ -261,6 +260,9 @@ func (b *builder) getChanSelectResult(expr *ssa.Extract) llvm.Value { func (b *builder) getSelectSendStorage(value ssa.Value) llvm.Value { typ := b.getLLVMType(value.Type()) + if b.isIndirectAggregate(typ) { + return b.getValuePointer(value) + } llvmValue := b.getValue(value, getPos(value)) ptr := llvmutil.CreateEntryBlockAlloca(b.Builder, typ, "select.send.value") b.CreateStore(llvmValue, ptr) diff --git a/compiler/compiler.go b/compiler/compiler.go index 5e2dcd993..9c1abb213 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -155,6 +155,8 @@ type builder struct { llvmFn llvm.Value info functionInfo locals map[ssa.Value]llvm.Value // local variables + indirectValues map[ssa.Value]llvm.Value + indirectReturn llvm.Value blockInfo []blockInfo currentBlock *ssa.BasicBlock currentBlockInfo *blockInfo @@ -193,6 +195,7 @@ func newBuilder(c *compilerContext, irbuilder llvm.Builder, f *ssa.Function) *bu llvmFn: fn, info: c.getFunctionInfo(f), locals: make(map[ssa.Value]llvm.Value), + indirectValues: make(map[ssa.Value]llvm.Value), dilocals: make(map[*types.Var]llvm.Metadata), } } @@ -1282,10 +1285,28 @@ func (b *builder) createFunctionStart(intrinsic bool) { // Load function parameters llvmParamIndex := 0 + if _, indirectResult := b.hasIndirectResult(b.fn.Signature); indirectResult && !b.info.exported { + b.indirectReturn = b.llvmFn.Param(llvmParamIndex) + b.indirectReturn.SetName("return") + llvmParamIndex++ + } for _, param := range b.fn.Params { llvmType := b.getLLVMType(param.Type()) + if b.isIndirectParam(llvmType, b.info.exported) { + llvmParam := b.llvmFn.Param(llvmParamIndex) + llvmParam.SetName(param.Name()) + b.indirectValues[param] = llvmParam + llvmParamIndex++ + continue + } + var paramInfos []paramInfo + if b.info.exported { + paramInfos = b.expandDirectFormalParamType(llvmType, param.Name(), param.Type()) + } else { + paramInfos = b.expandFormalParamType(llvmType, param.Name(), param.Type()) + } fields := make([]llvm.Value, 0, 1) - for _, info := range b.expandFormalParamType(llvmType, param.Name(), param.Type()) { + for _, info := range paramInfos { param := b.llvmFn.Param(llvmParamIndex) param.SetName(info.name) fields = append(fields, param) @@ -1423,7 +1444,7 @@ func (b *builder) createFunction() { for _, phi := range b.phis { block := phi.ssa.Block() for i, edge := range phi.ssa.Edges { - llvmVal := b.getValue(edge, getPos(phi.ssa)) + llvmVal := b.getCallArgument(edge, false) llvmBlock := b.blockInfo[block.Preds[i].Index].exit phi.llvm.AddIncoming([]llvm.Value{llvmVal}, []llvm.BasicBlock{llvmBlock}) } @@ -1432,6 +1453,9 @@ func (b *builder) createFunction() { if b.NeedsStackObjects { // Track phi nodes. for _, phi := range b.phis { + if b.isOversizedAggregate(phi.ssa.Type()) { + continue + } insertPoint := llvm.NextInstruction(phi.llvm) for !insertPoint.IsAPHINode().IsNil() { insertPoint = llvm.NextInstruction(insertPoint) @@ -1580,6 +1604,10 @@ func (b *builder) createInstruction(instr ssa.Instruction) { } func (b *builder) setValue(value ssa.Value, llvmValue llvm.Value) { + if b.isAggregateValue(value.Type()) && !llvmValue.IsNil() && llvmValue.Type().TypeKind() == llvm.PointerTypeKind { + b.indirectValues[value] = llvmValue + return + } b.locals[value] = llvmValue if len(*value.Referrers()) != 0 && b.NeedsStackObjects { b.trackExpr(value, llvmValue) @@ -1587,12 +1615,22 @@ func (b *builder) setValue(value ssa.Value, llvmValue llvm.Value) { } func (b *builder) createReturn(results []ssa.Value, pos token.Pos) { - switch len(results) { - case 0: + if len(results) == 0 { b.CreateRetVoid() - case 1: + } else if !b.indirectReturn.IsNil() { + if len(results) == 1 { + b.storeValue(b.indirectReturn, results[0]) + } else { + returnType := b.getLLVMResultType(b.fn.Signature) + for i, result := range results { + fieldPtr := b.CreateStructGEP(returnType, b.indirectReturn, i, "") + b.storeValue(fieldPtr, result) + } + } + b.CreateRetVoid() + } else if len(results) == 1 { b.CreateRet(b.getValue(results[0], pos)) - default: + } else { result := llvm.ConstNull(b.llvmFn.GlobalValueType().ReturnType()) for i, value := range results { result = b.CreateInsertValue(result, b.getValue(value, pos), i, "") @@ -1601,24 +1639,138 @@ func (b *builder) createReturn(results []ssa.Value, pos token.Pos) { } } +func (b *builder) isOversizedAggregate(typ types.Type) bool { + if !b.isAggregateValue(typ) { + return false + } + return b.isIndirectAggregate(b.getLLVMType(typ)) +} + +func (b *builder) isAggregateValue(typ types.Type) bool { + if tuple, ok := typ.(*types.Tuple); ok { + for i := 0; i < tuple.Len(); i++ { + if !isLLVMValueType(tuple.At(i).Type()) { + return false + } + } + } else { + switch typ.Underlying().(type) { + case *types.Array, *types.Struct: + default: + return false + } + if !isLLVMValueType(typ) { + return false + } + } + return true +} + +func (b *builder) getValuePointer(value ssa.Value) llvm.Value { + if ptr, ok := b.indirectValues[value]; ok { + return ptr + } + llvmType := b.getLLVMType(value.Type()) + ptr := b.createIndirectStorage(llvmType, value.Name()) + b.storeValue(ptr, value) + return ptr +} + +func (b *builder) getCallArgument(value ssa.Value, exported bool) llvm.Value { + paramType := b.getLLVMType(value.Type()) + if b.isIndirectParam(paramType, exported) { + return b.getValuePointer(value) + } + return b.getValue(value, getPos(value)) +} + +func (b *builder) createIndirectStorage(typ llvm.Type, name string) llvm.Value { + // Use runtime.alloc here so storage that escapes remains valid. The + // allocation optimizer moves bounded non-escaping storage to the stack. + size := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(typ), false) + layout := b.createObjectLayout(typ, b.fn.Pos()) + ptr := b.createAlloc(size, layout, b.targetData.ABITypeAlignment(typ), name) + if b.NeedsStackObjects { + b.trackPointer(ptr) + } + return ptr +} + +func (b *builder) copyIndirectAggregate(dst, src llvm.Value, typ llvm.Type) { + size := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(typ), false) + b.createMemCopy("memcpy", dst, src, size) +} + func (b *builder) storeValue(dst llvm.Value, value ssa.Value) { - b.CreateStore(b.getValue(value, getPos(value)), dst) + typ := b.getLLVMType(value.Type()) + if src, ok := b.indirectValues[value]; ok { + b.copyIndirectAggregate(dst, src, typ) + } else { + b.CreateStore(b.getValue(value, getPos(value)), dst) + } +} + +func (b *builder) copyToIndirectStorage(src llvm.Value, typ llvm.Type, name string) llvm.Value { + dst := b.createIndirectStorage(typ, name) + b.copyIndirectAggregate(dst, src, typ) + return dst } func (b *builder) loadFromStorage(ptr llvm.Value, typ types.Type, name string) llvm.Value { - return b.CreateLoad(b.getLLVMType(typ), ptr, name) + llvmType := b.getLLVMType(typ) + if b.isIndirectAggregate(llvmType) { + return b.copyToIndirectStorage(ptr, llvmType, name) + } + return b.CreateLoad(llvmType, ptr, name) } -func (b *builder) getValueStorage(value ssa.Value, name string) (ptr, size llvm.Value) { +func (b *builder) getValueField(value ssa.Value, index int, resultType types.Type, name string) (llvm.Value, bool) { + if !b.isAggregateValue(value.Type()) { + return llvm.Value{}, false + } + valueType := b.getLLVMType(value.Type()) + if _, indirect := b.indirectValues[value]; !indirect && !b.isIndirectAggregate(valueType) { + return llvm.Value{}, false + } + fieldPtr := b.CreateStructGEP(valueType, b.getValuePointer(value), index, "") + return b.loadFromStorage(fieldPtr, resultType, name), true +} + +func (b *builder) zeroIndirectStorage(ptr llvm.Value, typ llvm.Type) { + memset := b.getMemsetFunc() + b.createCall(memset.GlobalValueType(), memset, []llvm.Value{ + ptr, + llvm.ConstInt(b.ctx.Int8Type(), 0, false), + llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(typ), false), + llvm.ConstInt(b.ctx.Int1Type(), 0, false), + }, "") +} + +type valueStorage struct { + ptr, size llvm.Value + temporary bool +} + +func (b *builder) getValueStorage(value ssa.Value, name string) valueStorage { typ := b.getLLVMType(value.Type()) - ptr, size = b.createTemporaryAlloca(typ, name) + if b.isIndirectAggregate(typ) { + return valueStorage{ptr: b.getValuePointer(value)} + } + ptr, size := b.createTemporaryAlloca(typ, name) b.storeValue(ptr, value) - return ptr, size + return valueStorage{ptr: ptr, size: size, temporary: true} +} + +func (b *builder) endValueStorage(storage valueStorage) { + if storage.temporary { + b.emitLifetimeEnd(storage.ptr, storage.size) + } } type runtimeValueResult struct { valueType llvm.Type resultType llvm.Type + result llvm.Value valuePtr llvm.Value valueSize llvm.Value temporary bool @@ -1630,11 +1782,20 @@ func (b *builder) createRuntimeValueResult(valueType llvm.Type, commaOk, zeroAsN result := runtimeValueResult{ valueType: valueType, resultType: valueType, + valueSize: llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(valueType), false), commaOk: commaOk, } if commaOk { result.resultType = b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false) } + if b.isIndirectAggregate(result.resultType) { + result.result = b.createIndirectStorage(result.resultType, name+".result") + result.valuePtr = result.result + if commaOk { + result.valuePtr = b.CreateStructGEP(result.resultType, result.result, 0, "") + } + return result + } if zeroAsNull && b.targetData.TypeAllocSize(valueType) == 0 { result.valuePtr = llvm.ConstNull(b.dataPtrType) result.zero = true @@ -1646,6 +1807,13 @@ func (b *builder) createRuntimeValueResult(valueType llvm.Type, commaOk, zeroAsN } func (r runtimeValueResult) finish(b *builder, commaOk llvm.Value, name string) llvm.Value { + if !r.result.IsNil() { + if r.commaOk { + b.CreateStore(commaOk, b.CreateStructGEP(r.resultType, r.result, 1, "")) + } + return r.result + } + var value llvm.Value if r.zero { value = llvm.ConstNull(r.valueType) @@ -2157,7 +2325,7 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) var params []llvm.Value for _, param := range instr.Args { - params = append(params, b.getValue(param, getPos(instr))) + params = append(params, b.getCallArgument(param, exported)) } if instr.IsInvoke() { params = append([]llvm.Value{invokeReceiver}, params...) @@ -2165,6 +2333,13 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) } if !exported { + if resultType, indirectResult := b.hasIndirectResult(instr.Signature()); indirectResult { + result := b.createIndirectStorage(resultType, "call.result") + params = append([]llvm.Value{result}, params...) + params = append(params, context) + b.createInvoke(calleeType, callee, params, "") + return result, nil + } // This function takes a context parameter. // Add it to the end of the parameter list. params = append(params, context) @@ -2203,6 +2378,9 @@ func (b *builder) getValue(expr ssa.Value, pos token.Pos) llvm.Value { return value default: // other (local) SSA value + if value, ok := b.indirectValues[expr]; ok { + return b.CreateLoad(b.getLLVMType(expr.Type()), value, "") + } if value, ok := b.locals[expr]; ok { return value } else { @@ -2285,8 +2463,15 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { // This instruction changes the type, but the underlying value remains // the same. This is often a no-op, but sometimes we have to change the // LLVM type as well. - x := b.getValue(expr.X, getPos(expr)) llvmType := b.getLLVMType(expr.Type()) + if b.isIndirectAggregate(llvmType) { + sourceType := b.getLLVMType(expr.X.Type()) + if !b.isIndirectAggregate(sourceType) || b.targetData.TypeAllocSize(sourceType) != b.targetData.TypeAllocSize(llvmType) { + return llvm.Value{}, errors.New("todo: indirect aggregate ChangeType with different layout") + } + return b.getValuePointer(expr.X), nil + } + x := b.getValue(expr.X, getPos(expr)) if x.Type() == llvmType { // Different Go type but same LLVM type (for example, named int). // This is the common case. @@ -2316,9 +2501,15 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { if _, ok := expr.Tuple.(*ssa.Select); ok { return b.getChanSelectResult(expr), nil } + if value, ok := b.getValueField(expr.Tuple, expr.Index, expr.Type(), expr.Name()); ok { + return value, nil + } value := b.getValue(expr.Tuple, getPos(expr)) return b.CreateExtractValue(value, expr.Index, ""), nil case *ssa.Field: + if value, ok := b.getValueField(expr.X, expr.Field, expr.Type(), expr.Name()); ok { + return value, nil + } value := b.getValue(expr.X, getPos(expr)) result := b.CreateExtractValue(value, expr.Field, "") return result, nil @@ -2380,11 +2571,11 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { // Can't load directly from array (as index is non-constant), so // have to do it using an alloca+gep+load. arrayType := b.getLLVMType(expr.X.Type()) - alloca, allocaSize := b.getValueStorage(expr.X, "index.alloca") + storage := b.getValueStorage(expr.X, "index.alloca") zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) - ptr := b.CreateInBoundsGEP(arrayType, alloca, []llvm.Value{zero, index}, "index.gep") + ptr := b.CreateInBoundsGEP(arrayType, storage.ptr, []llvm.Value{zero, index}, "index.gep") result := b.loadFromStorage(ptr, expr.Type(), "index.load") - b.emitLifetimeEnd(alloca, allocaSize) + b.endValueStorage(storage) return result, nil default: panic("unknown *ssa.Index type") @@ -2453,6 +2644,11 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { case *ssa.MakeClosure: return b.parseMakeClosure(expr) case *ssa.MakeInterface: + if b.isOversizedAggregate(expr.X.Type()) { + typ := b.getLLVMType(expr.X.Type()) + ptr := b.copyToIndirectStorage(b.getValuePointer(expr.X), typ, "interface.value") + return b.createMakeInterfaceFromPointer(ptr, expr.X.Type()), nil + } val := b.getValue(expr.X, getPos(expr)) return b.createMakeInterface(val, expr.X.Type(), expr.Pos()), nil case *ssa.MakeMap: @@ -2519,7 +2715,8 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { return b.createMapIteratorNext(rangeVal, llvmRangeVal, it), nil } case *ssa.Phi: - phi := b.CreatePHI(b.getLLVMType(expr.Type()), "") + phiType := b.storedParamType(b.getLLVMType(expr.Type()), false) + phi := b.CreatePHI(phiType, "") b.phis = append(b.phis, phiNode{expr, phi}) return phi, nil case *ssa.Range: diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 0219544c7..6e666754c 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -131,6 +131,46 @@ func TestCompiler(t *testing.T) { } } +func TestOptimizedLargeAggregateABI(t *testing.T) { + options := &compileopts.Options{Target: "wasm"} + mod, errs := testCompilePackage(t, options, "large-optimized.go") + if len(errs) != 0 { + for _, err := range errs { + t.Error(err) + } + return + } + defer mod.Dispose() + + passOptions := llvm.NewPassBuilderOptions() + defer passOptions.Dispose() + if err := mod.RunPasses("default", llvm.TargetMachine{}, passOptions); err != nil { + t.Fatal(err) + } + + resultFn := mod.NamedFunction("main.makeLargeOptimizedValue") + if resultFn.IsNil() { + t.Fatal("missing function main.makeLargeOptimizedValue") + } + if resultType := resultFn.GlobalValueType().ReturnType(); resultType.TypeKind() != llvm.VoidTypeKind { + t.Errorf("large aggregate result was promoted to %s", resultType) + } + + for _, name := range []string{ + "main.makeLargeOptimizedValue", + "main.readLargeOptimizedValue", + "main.readMixedLargeOptimizedValue", + } { + fn := mod.NamedFunction(name) + if fn.IsNil() { + t.Fatalf("missing function %s", name) + } + if paramType := fn.GlobalValueType().ParamTypes()[0]; paramType.TypeKind() != llvm.PointerTypeKind { + t.Errorf("%s aggregate parameter was promoted to %s", name, paramType) + } + } +} + // normalizeIR canonicalizes LLVM IR so a single golden file keeps matching // across LLVM versions. Golden files are written against LLVM <21; newer LLVM // prints some attributes differently. @@ -284,6 +324,49 @@ func TestCompilerErrors(t *testing.T) { } } +func TestAggregateValueCount(t *testing.T) { + t.Parallel() + + ctx := llvm.NewContext() + defer ctx.Dispose() + + byteType := ctx.Int8Type() + tests := []struct { + name string + typ llvm.Type + count uint64 + exceeded bool + }{ + {"empty", llvm.ArrayType(byteType, 0), 0, false}, + {"limit", llvm.ArrayType(byteType, 1024), 1024, false}, + {"over limit", llvm.ArrayType(byteType, 1025), 0, true}, + {"combined limit", ctx.StructType([]llvm.Type{ + llvm.ArrayType(byteType, 512), + llvm.ArrayType(byteType, 512), + }, false), 1024, false}, + {"combined over limit", ctx.StructType([]llvm.Type{ + llvm.ArrayType(byteType, 1000), + llvm.ArrayType(byteType, 1000), + }, false), 0, true}, + {"comma-ok over limit", ctx.StructType([]llvm.Type{ + llvm.ArrayType(byteType, 1024), + ctx.Int1Type(), + }, false), 0, true}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + count, exceeded := aggregateValueCount(test.typ, 0) + if exceeded != test.exceeded { + t.Errorf("expected exceeded=%t, got %t", test.exceeded, exceeded) + } + if !exceeded && count != test.count { + t.Errorf("expected count=%d, got %d", test.count, count) + } + }) + } +} + // Build a package given a number of compiler options and a file. func testCompilePackage(t *testing.T, options *compileopts.Options, file string) (llvm.Module, []error) { target, err := compileopts.LoadTarget(options) diff --git a/compiler/defer.go b/compiler/defer.go index f38e053bd..522cd812d 100644 --- a/compiler/defer.go +++ b/compiler/defer.go @@ -406,7 +406,7 @@ func (b *builder) createDefer(instr *ssa.Defer) { var values llvmValueList lowerArgument := func(value ssa.Value) llvm.Value { - return b.getValue(value, getPos(instr)) + return b.getCallArgument(value, false) } if instr.Call.IsInvoke() { // Method call on an interface. @@ -438,7 +438,10 @@ func (b *builder) createDefer(instr *ssa.Defer) { // Collect all values to be put in the struct (starting with // runtime._defer fields). values = newLLVMValueList(callback, next) - values.appendSSAValues(instr.Call.Args, lowerArgument) + exported := b.getFunctionInfo(callee).exported + values.appendSSAValues(instr.Call.Args, func(value ssa.Value) llvm.Value { + return b.getCallArgument(value, exported) + }) } else if makeClosure, ok := instr.Call.Value.(*ssa.MakeClosure); ok { // Immediately applied function literal with free variables. @@ -612,9 +615,7 @@ func (b *builder) createRunDefers() { valueTypes = append(valueTypes, b.dataPtrType, b.dataPtrType) } - for _, arg := range callback.Args { - valueTypes = append(valueTypes, b.getLLVMType(arg.Type())) - } + valueTypes = b.appendStoredValueTypes(valueTypes, callback.Args, false) // Extract the params from the struct (including receiver). deferredCallType := b.ctx.StructType(valueTypes, false) @@ -647,6 +648,7 @@ func (b *builder) createRunDefers() { // with a strict calling convention. forwardParams = append(forwardParams, llvm.Undef(b.dataPtrType)) } + forwardParams = b.prependIndirectResult(callback.Signature(), false, forwardParams, "defer.result") b.createCall(fnType, fnPtr, forwardParams, "") @@ -655,9 +657,8 @@ func (b *builder) createRunDefers() { // Get the real defer struct type and cast to it. valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType} - for _, param := range getParams(callback.Signature) { - valueTypes = append(valueTypes, b.getLLVMType(param.Type())) - } + exported := b.getFunctionInfo(callback).exported + valueTypes = b.appendStoredParamTypes(valueTypes, getParams(callback.Signature), exported) deferredCallType := b.ctx.StructType(valueTypes, false) // Extract the params from the struct. @@ -665,11 +666,12 @@ func (b *builder) createRunDefers() { // Plain TinyGo functions add some extra parameters to implement async functionality and function receivers. // These parameters should not be supplied when calling into an external C/ASM function. - if !b.getFunctionInfo(callback).exported { + if !exported { // Add the context parameter. We know it is ignored by the receiving // function, but we have to pass one anyway. forwardParams = append(forwardParams, llvm.Undef(b.dataPtrType)) } + forwardParams = b.prependIndirectResult(callback.Signature, exported, forwardParams, "defer.result") // Call real function. fnType, fn := b.getFunction(callback) @@ -679,10 +681,7 @@ func (b *builder) createRunDefers() { // Get the real defer struct type and cast to it. fn := callback.Fn.(*ssa.Function) valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType} - params := fn.Signature.Params() - for v := range params.Variables() { - valueTypes = append(valueTypes, b.getLLVMType(v.Type())) - } + valueTypes = b.appendStoredParamTypes(valueTypes, getParams(fn.Signature), false) valueTypes = append(valueTypes, b.dataPtrType) // closure deferredCallType := b.ctx.StructType(valueTypes, false) @@ -691,6 +690,7 @@ func (b *builder) createRunDefers() { // Call deferred function. fnType, llvmFn := b.getFunction(fn) + forwardParams = b.prependIndirectResult(fn.Signature, false, forwardParams, "defer.result") b.createCall(fnType, llvmFn, forwardParams, "") case *ssa.Builtin: db := b.deferBuiltinFuncs[callback] diff --git a/compiler/func.go b/compiler/func.go index c5a48a7fe..260ff07da 100644 --- a/compiler/func.go +++ b/compiler/func.go @@ -10,6 +10,11 @@ import ( "tinygo.org/x/go-llvm" ) +// LLVM recursively expands each struct field and array element in parameters +// and results into separate values. It gets very slow with too many values, so +// pass larger aggregates indirectly before LLVM expands them. +const maxDirectAggregateValues = 1024 + func (c *compilerContext) getLLVMResultType(sig *types.Signature) llvm.Type { switch sig.Results().Len() { case 0: @@ -25,6 +30,71 @@ func (c *compilerContext) getLLVMResultType(sig *types.Signature) llvm.Type { } } +func (c *compilerContext) hasIndirectResult(sig *types.Signature) (llvm.Type, bool) { + resultType := c.getLLVMResultType(sig) + return resultType, c.isIndirectAggregate(resultType) +} + +func (c *compilerContext) isIndirectAggregate(typ llvm.Type) bool { + switch typ.TypeKind() { + case llvm.ArrayTypeKind, llvm.StructTypeKind: + _, exceeded := aggregateValueCount(typ, 0) + return exceeded + default: + return false + } +} + +func aggregateValueCount(typ llvm.Type, count uint64) (uint64, bool) { + switch typ.TypeKind() { + case llvm.ArrayTypeKind: + length := uint64(typ.ArrayLength()) + if length == 0 { + return count, false + } + elementCount, exceeded := aggregateValueCount(typ.ElementType(), 0) + if exceeded { + return count, true + } + if elementCount != 0 && length > (maxDirectAggregateValues-count)/elementCount { + return count, true + } + return count + length*elementCount, false + case llvm.StructTypeKind: + for _, field := range typ.StructElementTypes() { + var exceeded bool + count, exceeded = aggregateValueCount(field, count) + if exceeded { + return count, true + } + } + return count, false + default: + count++ + return count, count > maxDirectAggregateValues + } +} + +func isLLVMValueType(typ types.Type) bool { + switch typ := typ.Underlying().(type) { + case *types.Basic: + return typ.Kind() != types.Invalid + case *types.Array: + return isLLVMValueType(typ.Elem()) + case *types.Struct: + for field := range typ.Fields() { + if !isLLVMValueType(field.Type()) { + return false + } + } + return true + case *types.Chan, *types.Interface, *types.Map, *types.Pointer, *types.Signature, *types.Slice: + return true + default: + return false + } +} + // createFuncValue creates a function value from a raw function pointer with no // context. func (b *builder) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) llvm.Value { @@ -63,10 +133,18 @@ func (c *compilerContext) getFuncType(typ *types.Signature) llvm.Type { // getLLVMFunctionType returns a LLVM function type for a given signature. func (c *compilerContext) getLLVMFunctionType(typ *types.Signature) llvm.Type { - returnType := c.getLLVMResultType(typ) + returnType, indirectResult := c.hasIndirectResult(typ) // Get the parameter types. var paramTypes []llvm.Type + if indirectResult { + // LLVM expands aggregate returns into scalar leaves before deciding + // whether to pass them indirectly, so a large IR return can exhaust + // memory. Returning void avoids that expansion and cannot be demoted + // again. Keep the result pointer first so the context remains last. + paramTypes = append(paramTypes, c.dataPtrType) + returnType = c.ctx.VoidType() + } if typ.Recv() != nil { recv := c.getLLVMType(typ.Recv().Type()) if recv.StructName() == "runtime._interface" { diff --git a/compiler/goroutine.go b/compiler/goroutine.go index 741d79afb..26d489a3a 100644 --- a/compiler/goroutine.go +++ b/compiler/goroutine.go @@ -53,6 +53,7 @@ func (b *builder) createGo(instr *ssa.Go) { var funcType llvm.Type var context llvm.Value hasContext := false + exported := false if callee := instr.Call.StaticCallee(); callee != nil { // Static callee is known. This makes it easier to start a new // goroutine. @@ -71,6 +72,7 @@ func (b *builder) createGo(instr *ssa.Go) { hasContext = true } funcType, funcPtr = b.getFunction(callee) + exported = b.getFunctionInfo(callee).exported } else if instr.Call.IsInvoke() { // This is a method call on an interface value. itf := b.getValue(instr.Call.Value, getPos(instr)) @@ -93,7 +95,7 @@ func (b *builder) createGo(instr *ssa.Go) { } for _, param := range instr.Call.Args { - params = append(params, b.getGoroutineCallArgument(param)...) + params = append(params, b.getGoroutineCallArgument(param, exported)...) } if !context.IsNil() { params = append(params, context) @@ -101,6 +103,7 @@ func (b *builder) createGo(instr *ssa.Go) { if hasContext && instr.Call.StaticCallee() == nil { params = append(params, funcPtr) } + params = b.prependIndirectResult(instr.Call.Signature(), exported, params, "go.result") paramBundle := b.emitPointerPack(params, instr.Pos()) var stackSize llvm.Value @@ -124,8 +127,13 @@ func (b *builder) createGo(instr *ssa.Go) { b.createCall(fnType, start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.dataPtrType)}, "") } -func (b *builder) getGoroutineCallArgument(value ssa.Value) []llvm.Value { - return b.expandFormalParam(b.getValue(value, getPos(value))) +func (b *builder) getGoroutineCallArgument(value ssa.Value, exported bool) []llvm.Value { + typ := b.getLLVMType(value.Type()) + arg := b.getCallArgument(value, exported) + if b.isIndirectParam(typ, exported) { + return []llvm.Value{b.copyToIndirectStorage(arg, typ, "go.param")} + } + return b.expandFormalParam(arg) } // Create an exported wrapper function for functions with the //go:wasmexport diff --git a/compiler/interface.go b/compiler/interface.go index c94f88175..b93032015 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -85,6 +85,10 @@ const ( // An interface value is a {typecode, value} tuple named runtime._interface. func (b *builder) createMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value { itfValue := b.emitPointerPack([]llvm.Value{val}, pos) + return b.createMakeInterfaceFromPointer(itfValue, typ) +} + +func (b *builder) createMakeInterfaceFromPointer(itfValue llvm.Value, typ types.Type) llvm.Value { itfType := b.getTypeCode(typ) itf := llvm.Undef(b.getLLVMRuntimeType("_interface")) itf = b.CreateInsertValue(itf, itfType, 0, "") @@ -98,9 +102,16 @@ func (b *builder) createMakeInterface(val llvm.Value, typ types.Type, pos token. // doesn't match the underlying type of the interface. func (b *builder) extractValueFromInterface(itf llvm.Value, llvmType llvm.Type) llvm.Value { valuePtr := b.CreateExtractValue(itf, 1, "typeassert.value.ptr") + if b.isIndirectAggregate(llvmType) { + return valuePtr + } return b.emitPointerUnpack(valuePtr, []llvm.Type{llvmType})[0] } +func (b *builder) extractValuePointerFromInterface(itf llvm.Value) llvm.Value { + return b.CreateExtractValue(itf, 1, "typeassert.value.ptr") +} + func (c *compilerContext) pkgPathPtr(pkgpath string) llvm.Value { pkgpathName := "reflect/types.type.pkgpath.empty" if pkgpath != "" { @@ -1067,6 +1078,21 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value { if expr.CommaOk { nextBlock := b.insertBasicBlock("typeassert.next") b.currentBlockInfo.exit = nextBlock + if b.isIndirectAggregate(assertedType) { + resultType := b.getLLVMType(expr.Type()) + result := b.createIndirectStorage(resultType, "typeassert.result") + b.zeroIndirectStorage(result, resultType) + b.CreateCondBr(commaOk, okBlock, nextBlock) + + b.SetInsertPointAtEnd(okBlock) + valuePtr := b.extractValuePointerFromInterface(itf) + b.copyIndirectAggregate(b.CreateStructGEP(resultType, result, 0, ""), valuePtr, assertedType) + b.CreateBr(nextBlock) + + b.SetInsertPointAtEnd(nextBlock) + b.CreateStore(commaOk, b.CreateStructGEP(resultType, result, 1, "")) + return result + } b.CreateCondBr(commaOk, okBlock, nextBlock) // Retrieve the value from the interface if the type assert was @@ -1206,6 +1232,9 @@ func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value { llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType) c.addStandardDeclaredAttributes(llvmFn) llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-invoke", c.getMethodSignatureName(instr.Method))) + if _, indirect := c.hasIndirectResult(sig); indirect { + llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-indirect-result", "true")) + } methods := c.getMethodsString(instr.Value.Type().Underlying().(*types.Interface)) llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-methods", methods)) } @@ -1251,6 +1280,7 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFnType // Get the expanded receiver type. receiverType := c.getLLVMType(fn.Signature.Recv().Type()) var expandedReceiverType []llvm.Type + receiverIndirect := c.isIndirectAggregate(receiverType) for _, info := range c.expandFormalParamType(receiverType, "", nil) { expandedReceiverType = append(expandedReceiverType, info.llvmType) } @@ -1265,7 +1295,13 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFnType } // create wrapper function - paramTypes := append([]llvm.Type{c.dataPtrType}, llvmFnType.ParamTypes()[len(expandedReceiverType):]...) + resultOffset := 0 + if _, indirect := c.hasIndirectResult(fn.Signature); indirect { + resultOffset = 1 + } + paramTypes := append([]llvm.Type{}, llvmFnType.ParamTypes()[:resultOffset]...) + paramTypes = append(paramTypes, c.dataPtrType) + paramTypes = append(paramTypes, llvmFnType.ParamTypes()[resultOffset+len(expandedReceiverType):]...) wrapFnType := llvm.FunctionType(llvmFnType.ReturnType(), paramTypes, false) wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType) c.addStandardAttributes(wrapper) @@ -1291,8 +1327,15 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFnType block := b.ctx.AddBasicBlock(wrapper, "entry") b.SetInsertPointAtEnd(block) - receiverValue := b.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0] - params := append(b.expandFormalParam(receiverValue), wrapper.Params()[1:]...) + params := append([]llvm.Value{}, wrapper.Params()[:resultOffset]...) + receiverParam := wrapper.Param(resultOffset) + if receiverIndirect { + params = append(params, receiverParam) + } else { + receiverValue := b.emitPointerUnpack(receiverParam, []llvm.Type{receiverType})[0] + params = append(params, b.expandFormalParam(receiverValue)...) + } + params = append(params, wrapper.Params()[resultOffset+1:]...) if llvmFnType.ReturnType().TypeKind() == llvm.VoidTypeKind { b.CreateCall(llvmFnType, llvmFn, params, "") b.CreateRetVoid() diff --git a/compiler/map.go b/compiler/map.go index b2478b19c..3481ae90a 100644 --- a/compiler/map.go +++ b/compiler/map.go @@ -100,14 +100,14 @@ func (b *builder) createMapLookup(keyType, valueType types.Type, m llvm.Value, k } else { // Key stored at actual type: either binary-comparable or with // compiler-generated hash/equal. - mapKeyAlloca, mapKeySize := b.getValueStorage(key, "hashmap.key") - params := []llvm.Value{m, mapKeyAlloca, mapValueAlloca, mapValueSize} + mapKey := b.getValueStorage(key, "hashmap.key") + params := []llvm.Value{m, mapKey.ptr, mapValueAlloca, mapValueSize} fnName := "hashmapBinaryGet" if !hashmapIsBinaryKey(keyType) { fnName = "hashmapGenericGet" } commaOkValue = b.createRuntimeCall(fnName, params, "") - b.emitLifetimeEnd(mapKeyAlloca, mapKeySize) + b.endValueStorage(mapKey) } // The value is set to the zero value if the key doesn't exist. @@ -117,24 +117,24 @@ func (b *builder) createMapLookup(keyType, valueType types.Type, m llvm.Value, k // createMapUpdate updates a map key to a given value, by creating an // appropriate runtime call. func (b *builder) createMapUpdate(keyType types.Type, m llvm.Value, key, value ssa.Value, pos token.Pos) { - valueAlloca, valueSize := b.getValueStorage(value, "hashmap.value") + storedValue := b.getValueStorage(value, "hashmap.value") keyType = keyType.Underlying() if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { // key is a string - params := []llvm.Value{m, b.getValue(key, getPos(key)), valueAlloca} + params := []llvm.Value{m, b.getValue(key, getPos(key)), storedValue.ptr} b.createRuntimeInvoke("hashmapStringSet", params, "") } else { // Key stored at actual type. - keyAlloca, keySize := b.getValueStorage(key, "hashmap.key") + keyStorage := b.getValueStorage(key, "hashmap.key") fnName := "hashmapBinarySet" if !hashmapIsBinaryKey(keyType) { fnName = "hashmapGenericSet" } - params := []llvm.Value{m, keyAlloca, valueAlloca} + params := []llvm.Value{m, keyStorage.ptr, storedValue.ptr} b.createRuntimeInvoke(fnName, params, "") - b.emitLifetimeEnd(keyAlloca, keySize) + b.endValueStorage(keyStorage) } - b.emitLifetimeEnd(valueAlloca, valueSize) + b.endValueStorage(storedValue) } // createMapDelete deletes a key from a map by calling the appropriate runtime diff --git a/compiler/symbol.go b/compiler/symbol.go index 891981fb5..944f74240 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -79,13 +79,27 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value) return llvmFn.GlobalValueType(), llvmFn } - retType := c.getLLVMResultType(fn.Signature) + retType, indirectResult := c.hasIndirectResult(fn.Signature) + if info.exported { + indirectResult = false + } var paramInfos []paramInfo + if indirectResult { + paramInfos = append(paramInfos, paramInfo{ + llvmType: c.dataPtrType, + name: "return", + elemSize: c.targetData.TypeAllocSize(retType), + }) + retType = c.ctx.VoidType() + } for _, param := range getParams(fn.Signature) { paramType := c.getLLVMType(param.Type()) - paramFragmentInfos := c.expandFormalParamType(paramType, param.Name(), param.Type()) - paramInfos = append(paramInfos, paramFragmentInfos...) + if info.exported { + paramInfos = append(paramInfos, c.expandDirectFormalParamType(paramType, param.Name(), param.Type())...) + } else { + paramInfos = append(paramInfos, c.expandFormalParamType(paramType, param.Name(), param.Type())...) + } } // Add an extra parameter as the function context. This context is used in @@ -95,12 +109,20 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value) } var paramTypes []llvm.Type + hasIndirectABI := indirectResult for _, info := range paramInfos { paramTypes = append(paramTypes, info.llvmType) + hasIndirectABI = hasIndirectABI || info.flags¶mIsIndirect != 0 } fnType := llvm.FunctionType(retType, paramTypes, info.variadic) llvmFn = llvm.AddFunction(c.mod, info.linkName, fnType) + if hasIndirectABI { + // Argument promotion only rewrites functions whose uses are all direct + // calls. Keep an address use so LLVM cannot reconstruct the large + // aggregate signature that this ABI exists to avoid. + llvmutil.AppendToGlobal(c.mod, "llvm.used", llvmFn) + } if strings.HasPrefix(c.Triple, "wasm") { // C functions without prototypes like this: // void foo(); diff --git a/compiler/testdata/large-optimized.go b/compiler/testdata/large-optimized.go new file mode 100644 index 000000000..91a0740fa --- /dev/null +++ b/compiler/testdata/large-optimized.go @@ -0,0 +1,20 @@ +package main + +type largeOptimizedValue [1025]byte + +type mixedLargeOptimizedValue struct { + value [1025]byte + any any +} + +func makeLargeOptimizedValue() largeOptimizedValue { + return largeOptimizedValue{} +} + +func readLargeOptimizedValue(value largeOptimizedValue) byte { + return value[len(value)-1] +} + +func readMixedLargeOptimizedValue(value mixedLargeOptimizedValue) byte { + return value.value[len(value.value)-1] +} diff --git a/compiler/testdata/large.ll b/compiler/testdata/large.ll index 715c3e9a6..27da84606 100644 --- a/compiler/testdata/large.ll +++ b/compiler/testdata/large.ll @@ -4,12 +4,12 @@ target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-n target triple = "wasm32-unknown-wasi" %runtime._string = type { ptr, i32 } -%main.largeStruct = type { [1025 x i8], ptr } %runtime.channelOp = type { ptr, ptr, i32, ptr } %runtime.chanSelectState = type { ptr, ptr } @"runtime/gc.layout:258-000000000000000000000000000000000000000000000000000000000000000002" = linkonce_odr unnamed_addr constant { i32, [33 x i8] } { i32 258, [33 x i8] c"\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\02" } @"reflect/types.typeid:named:main.largeValue" = external constant i8 +@llvm.used = appending global [15 x ptr] [ptr @"(main.largeReceiver).makeLargeValue", ptr @"(main.largeReceiver).readLargeValue", ptr @main.makeLargeValue, ptr @main.makeZeroLargeValue, ptr @main.readLargeValue, ptr @main.deferLargeValue, ptr @main.goLargeValue, ptr @main.makeLargeResults, ptr @main.makeTwoLargeResults, ptr @main.makeMixedLargeResults, ptr @main.chooseLargeValue, ptr @main.makePointerLargeValue, ptr @main.useLargeMap, ptr @main.useLargeChannel, ptr @main.selectLargeChannel] @"main$string" = internal unnamed_addr constant [31 x i8] c"blocking select matched no case", align 1 @"main$pack" = internal unnamed_addr constant { %runtime._string } { %runtime._string { ptr @"main$string", i32 31 } } @"reflect/types.type:basic:string" = linkonce_odr constant { i8, ptr } { i8 81, ptr @"reflect/types.type:pointer:basic:string" }, align 4 @@ -24,58 +24,70 @@ entry: } ; Function Attrs: nounwind -define hidden [1025 x i8] @"(main.largeReceiver).makeLargeValue"([1025 x i8] %receiver, ptr %context) unnamed_addr #1 { +define hidden void @"(main.largeReceiver).makeLargeValue"(ptr dereferenceable_or_null(1025) %return, ptr readonly dereferenceable_or_null(1025) %receiver, ptr %context) unnamed_addr #1 { entry: - ret [1025 x i8] %receiver + call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %return, ptr noundef nonnull align 1 dereferenceable(1025) %receiver, i32 1025, i1 false) + ret void } +; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite) +declare void @llvm.memcpy.p0.p0.i32(ptr noalias writeonly captures(none), ptr noalias readonly captures(none), i32, i1 immarg) #2 + ; Function Attrs: nounwind -define hidden i8 @"(main.largeReceiver).readLargeValue"([1025 x i8] %receiver, [1025 x i8] %value, ptr %context) unnamed_addr #1 { +define hidden i8 @"(main.largeReceiver).readLargeValue"(ptr readonly dereferenceable_or_null(1025) %receiver, ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 { entry: %stackalloc = alloca i8, align 1 - %value1 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #7 - call void @runtime.trackPointer(ptr nonnull %value1, ptr nonnull %stackalloc, ptr undef) #7 - store [1025 x i8] %value, ptr %value1, align 1 + %value1 = 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 %value1, ptr nonnull %stackalloc, ptr undef) #9 + call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %value1, ptr noundef nonnull align 1 dereferenceable(1025) %value, i32 1025, i1 false) %0 = getelementptr inbounds nuw i8, ptr %value1, i32 1024 %1 = load i8, ptr %0, align 1 ret i8 %1 } ; Function Attrs: allockind("alloc,zeroed") allocsize(0) -declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #2 +declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #3 ; Function Attrs: nounwind -define hidden [1025 x i8] @main.makeLargeValue(i8 %value, ptr %context) unnamed_addr #1 { +define hidden void @main.makeLargeValue(ptr dereferenceable_or_null(1025) %return, i8 %value, ptr %context) unnamed_addr #1 { entry: %stackalloc = alloca i8, align 1 - %result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #7 - call void @runtime.trackPointer(ptr nonnull %result, ptr nonnull %stackalloc, ptr undef) #7 + %result = 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 %result, ptr nonnull %stackalloc, ptr undef) #9 %0 = getelementptr inbounds nuw i8, ptr %result, i32 1024 store i8 %value, ptr %0, align 1 - %1 = load [1025 x i8], ptr %result, align 1 - ret [1025 x i8] %1 + %1 = 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 %1, ptr nonnull %stackalloc, ptr undef) #9 + call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %1, ptr noundef nonnull align 1 dereferenceable(1025) %result, i32 1025, i1 false) + call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %return, ptr noundef nonnull align 1 dereferenceable(1025) %1, i32 1025, i1 false) + ret void } ; Function Attrs: nounwind -define hidden [1025 x i8] @main.makeZeroLargeValue(ptr %context) unnamed_addr #1 { +define hidden void @main.makeZeroLargeValue(ptr dereferenceable_or_null(1025) %return, ptr %context) unnamed_addr #1 { entry: - ret [1025 x i8] zeroinitializer + store [1025 x i8] zeroinitializer, ptr %return, align 1 + ret void } ; Function Attrs: nounwind define hidden i8 @main.passZeroLargeValue(ptr %context) unnamed_addr #1 { entry: - %0 = call i8 @main.readLargeValue([1025 x i8] zeroinitializer, ptr undef) + %stackalloc = alloca i8, align 1 + %"main.largeValue{}:main.largeValue" = 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 %"main.largeValue{}:main.largeValue", ptr nonnull %stackalloc, ptr undef) #9 + store [1025 x i8] zeroinitializer, ptr %"main.largeValue{}:main.largeValue", align 1 + %0 = call i8 @main.readLargeValue(ptr nonnull %"main.largeValue{}:main.largeValue", ptr undef) ret i8 %0 } ; Function Attrs: nounwind -define hidden i8 @main.readLargeValue([1025 x i8] %value, ptr %context) unnamed_addr #1 { +define hidden i8 @main.readLargeValue(ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 { entry: %stackalloc = alloca i8, align 1 - %value1 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #7 - call void @runtime.trackPointer(ptr nonnull %value1, ptr nonnull %stackalloc, ptr undef) #7 - store [1025 x i8] %value, ptr %value1, align 1 + %value1 = 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 %value1, ptr nonnull %stackalloc, ptr undef) #9 + call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %value1, ptr noundef nonnull align 1 dereferenceable(1025) %value, i32 1025, i1 false) %0 = getelementptr inbounds nuw i8, ptr %value1, i32 1024 %1 = load i8, ptr %0, align 1 ret i8 %1 @@ -84,24 +96,30 @@ entry: ; Function Attrs: nounwind define hidden i8 @main.useLargeValue(ptr %context) unnamed_addr #1 { entry: - %0 = call [1025 x i8] @main.makeLargeValue(i8 42, ptr undef) - %1 = call i8 @main.readLargeValue([1025 x i8] %0, ptr undef) - ret i8 %1 + %stackalloc = alloca i8, align 1 + %call.result = 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 %call.result, ptr nonnull %stackalloc, ptr undef) #9 + call void @main.makeLargeValue(ptr nonnull %call.result, i8 42, ptr undef) + %0 = call i8 @main.readLargeValue(ptr nonnull %call.result, ptr undef) + ret i8 %0 } ; Function Attrs: nounwind define hidden i8 @main.useLargeFunctionValue(ptr %fn.context, ptr %fn.funcptr, ptr %context) unnamed_addr #1 { entry: - %0 = call [1025 x i8] @main.makeLargeValue(i8 42, ptr undef) - %1 = icmp eq ptr %fn.funcptr, null - br i1 %1, label %fpcall.throw, label %fpcall.next + %stackalloc = alloca i8, align 1 + %call.result = 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 %call.result, ptr nonnull %stackalloc, ptr undef) #9 + call void @main.makeLargeValue(ptr nonnull %call.result, i8 42, ptr undef) + %0 = icmp eq ptr %fn.funcptr, null + br i1 %0, label %fpcall.throw, label %fpcall.next fpcall.next: ; preds = %entry - %2 = call i8 %fn.funcptr([1025 x i8] %0, ptr %fn.context) #7 - ret i8 %2 + %1 = call i8 %fn.funcptr(ptr nonnull %call.result, ptr %fn.context) #9 + ret i8 %1 fpcall.throw: ; preds = %entry - call void @runtime.nilPanic(ptr undef) #7 + call void @runtime.nilPanic(ptr undef) #9 unreachable } @@ -110,25 +128,31 @@ declare void @runtime.nilPanic(ptr) #0 ; Function Attrs: nounwind define hidden i8 @main.useLargeInterface(ptr %value.typecode, ptr %value.value, ptr %context) unnamed_addr #1 { entry: - %0 = call [1025 x i8] @"interface:{main.makeLargeValue:func:{}{named:main.largeValue},main.readLargeValue:func:{named:main.largeValue}{basic:uint8}}.makeLargeValue$invoke"(ptr %value.value, ptr %value.typecode, ptr undef) #7 - %1 = call i8 @"interface:{main.makeLargeValue:func:{}{named:main.largeValue},main.readLargeValue:func:{named:main.largeValue}{basic:uint8}}.readLargeValue$invoke"(ptr %value.value, [1025 x i8] %0, ptr %value.typecode, ptr undef) #7 - ret i8 %1 + %stackalloc = alloca i8, align 1 + %call.result = 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 %call.result, ptr nonnull %stackalloc, ptr undef) #9 + call void @"interface:{main.makeLargeValue:func:{}{named:main.largeValue},main.readLargeValue:func:{named:main.largeValue}{basic:uint8}}.makeLargeValue$invoke"(ptr nonnull %call.result, ptr %value.value, ptr %value.typecode, ptr undef) #9 + %0 = call i8 @"interface:{main.makeLargeValue:func:{}{named:main.largeValue},main.readLargeValue:func:{named:main.largeValue}{basic:uint8}}.readLargeValue$invoke"(ptr %value.value, ptr nonnull %call.result, ptr %value.typecode, ptr undef) #9 + ret i8 %0 } -declare [1025 x i8] @"interface:{main.makeLargeValue:func:{}{named:main.largeValue},main.readLargeValue:func:{named:main.largeValue}{basic:uint8}}.makeLargeValue$invoke"(ptr, ptr, ptr) #3 +declare void @"interface:{main.makeLargeValue:func:{}{named:main.largeValue},main.readLargeValue:func:{named:main.largeValue}{basic:uint8}}.makeLargeValue$invoke"(ptr, ptr, ptr, ptr) #4 -declare i8 @"interface:{main.makeLargeValue:func:{}{named:main.largeValue},main.readLargeValue:func:{named:main.largeValue}{basic:uint8}}.readLargeValue$invoke"(ptr, [1025 x i8], ptr, ptr) #4 +declare i8 @"interface:{main.makeLargeValue:func:{}{named:main.largeValue},main.readLargeValue:func:{named:main.largeValue}{basic:uint8}}.readLargeValue$invoke"(ptr, ptr, ptr, ptr) #5 ; Function Attrs: nounwind -define hidden void @main.deferLargeValue([1025 x i8] %value, ptr %context) unnamed_addr #1 { +define hidden void @main.deferLargeValue(ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 { entry: - %defer.alloca = alloca { i32, ptr, [1025 x i8] }, align 8 + %defer.alloca = alloca { i32, ptr, ptr }, align 8 %deferPtr = alloca ptr, align 4 store ptr null, ptr %deferPtr, align 4 %stackalloc = alloca i8, align 1 - %0 = insertvalue { i32, ptr, [1025 x i8] } zeroinitializer, [1025 x i8] %value, 2 - call void @runtime.trackPointer(ptr nonnull %defer.alloca, ptr nonnull %stackalloc, ptr undef) #7 - store { i32, ptr, [1025 x i8] } %0, ptr %defer.alloca, align 4 + call void @runtime.trackPointer(ptr nonnull %defer.alloca, ptr nonnull %stackalloc, ptr undef) #9 + store i32 0, ptr %defer.alloca, align 4 + %defer.alloca.repack1 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 4 + store ptr null, ptr %defer.alloca.repack1, align 4 + %defer.alloca.repack3 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 8 + store ptr %value, ptr %defer.alloca.repack3, align 4 store ptr %defer.alloca, ptr %deferPtr, align 4 br label %rundefers.block @@ -139,23 +163,23 @@ rundefers.block: ; preds = %entry br label %rundefers.loophead rundefers.loophead: ; preds = %rundefers.callback0, %rundefers.block - %1 = load ptr, ptr %deferPtr, align 4 - %stackIsNil = icmp eq ptr %1, null + %0 = load ptr, ptr %deferPtr, align 4 + %stackIsNil = icmp eq ptr %0, null br i1 %stackIsNil, label %rundefers.end, label %rundefers.loop rundefers.loop: ; preds = %rundefers.loophead - %stack.next.gep = getelementptr inbounds nuw i8, ptr %1, i32 4 + %stack.next.gep = getelementptr inbounds nuw i8, ptr %0, i32 4 %stack.next = load ptr, ptr %stack.next.gep, align 4 store ptr %stack.next, ptr %deferPtr, align 4 - %callback = load i32, ptr %1, align 4 + %callback = load i32, ptr %0, align 4 switch i32 %callback, label %rundefers.default [ i32 0, label %rundefers.callback0 ] rundefers.callback0: ; preds = %rundefers.loop - %gep = getelementptr inbounds nuw i8, ptr %1, i32 8 - %param = load [1025 x i8], ptr %gep, align 1 - %2 = call i8 @main.readLargeValue([1025 x i8] %param, ptr undef) + %gep = getelementptr inbounds nuw i8, ptr %0, i32 8 + %param = load ptr, ptr %gep, align 4 + %1 = call i8 @main.readLargeValue(ptr %param, ptr undef) br label %rundefers.loophead rundefers.default: ; preds = %rundefers.loop @@ -169,92 +193,115 @@ recover: ; No predecessors! } ; Function Attrs: nounwind -define hidden void @main.goLargeValue([1025 x i8] %value, ptr %context) unnamed_addr #1 { +define hidden void @main.goLargeValue(ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 { entry: %stackalloc = alloca i8, align 1 - %0 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #7 - call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #7 - store [1025 x i8] %value, ptr %0, align 1 - call void @"internal/task.start"(i32 ptrtoint (ptr @"main.readLargeValue$gowrapper" to i32), ptr nonnull %0, i32 65536, ptr undef) #7 + %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 ret void } declare void @runtime.deadlock(ptr) #0 ; Function Attrs: nounwind -define linkonce_odr void @"main.readLargeValue$gowrapper"(ptr %0) unnamed_addr #5 { +define linkonce_odr void @"main.readLargeValue$gowrapper"(ptr %0) unnamed_addr #6 { entry: - %1 = load [1025 x i8], ptr %0, align 1 - %2 = call i8 @main.readLargeValue([1025 x i8] %1, ptr undef) - call void @runtime.deadlock(ptr undef) #7 + %1 = call i8 @main.readLargeValue(ptr %0, ptr undef) + call void @runtime.deadlock(ptr undef) #9 unreachable } declare void @"internal/task.start"(i32, ptr, i32, ptr) #0 ; Function Attrs: nounwind -define hidden { [1025 x i8], i8 } @main.makeLargeResults(i8 %value, ptr %context) unnamed_addr #1 { +define hidden void @main.makeLargeResults(ptr dereferenceable_or_null(1026) %return, i8 %value, ptr %context) unnamed_addr #1 { entry: - %0 = call [1025 x i8] @main.makeLargeValue(i8 %value, ptr undef) - %1 = insertvalue { [1025 x i8], i8 } zeroinitializer, [1025 x i8] %0, 0 - %2 = insertvalue { [1025 x i8], i8 } %1, i8 %value, 1 - ret { [1025 x i8], i8 } %2 + %stackalloc = alloca i8, align 1 + %call.result = 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 %call.result, ptr nonnull %stackalloc, ptr undef) #9 + call void @main.makeLargeValue(ptr nonnull %call.result, i8 %value, ptr undef) + call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %return, ptr noundef nonnull align 1 dereferenceable(1025) %call.result, i32 1025, i1 false) + %0 = getelementptr inbounds nuw i8, ptr %return, i32 1025 + store i8 %value, ptr %0, align 1 + ret void } ; Function Attrs: nounwind -define hidden { [1025 x i8], [1025 x i8] } @main.makeTwoLargeResults(i8 %value, ptr %context) unnamed_addr #1 { +define hidden void @main.makeTwoLargeResults(ptr dereferenceable_or_null(2050) %return, i8 %value, ptr %context) unnamed_addr #1 { entry: - %0 = call [1025 x i8] @main.makeLargeValue(i8 %value, ptr undef) - %1 = add i8 %value, 1 - %2 = call [1025 x i8] @main.makeLargeValue(i8 %1, ptr undef) - %3 = insertvalue { [1025 x i8], [1025 x i8] } zeroinitializer, [1025 x i8] %0, 0 - %4 = insertvalue { [1025 x i8], [1025 x i8] } %3, [1025 x i8] %2, 1 - ret { [1025 x i8], [1025 x i8] } %4 + %stackalloc = alloca i8, align 1 + %call.result = 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 %call.result, ptr nonnull %stackalloc, ptr undef) #9 + call void @main.makeLargeValue(ptr nonnull %call.result, i8 %value, ptr undef) + %0 = add i8 %value, 1 + %call.result1 = 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 %call.result1, ptr nonnull %stackalloc, ptr undef) #9 + call void @main.makeLargeValue(ptr nonnull %call.result1, i8 %0, ptr undef) + call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %return, ptr noundef nonnull align 1 dereferenceable(1025) %call.result, i32 1025, i1 false) + %1 = getelementptr inbounds nuw i8, ptr %return, i32 1025 + call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %1, ptr noundef nonnull align 1 dereferenceable(1025) %call.result1, i32 1025, i1 false) + ret void } ; Function Attrs: nounwind -define hidden { [1025 x i8], i8, [1025 x i8] } @main.makeMixedLargeResults(i8 %value, ptr %context) unnamed_addr #1 { +define hidden void @main.makeMixedLargeResults(ptr dereferenceable_or_null(2051) %return, i8 %value, ptr %context) unnamed_addr #1 { entry: - %0 = call [1025 x i8] @main.makeLargeValue(i8 %value, ptr undef) - %1 = add i8 %value, 1 - %2 = add i8 %value, 2 - %3 = call [1025 x i8] @main.makeLargeValue(i8 %2, ptr undef) - %4 = insertvalue { [1025 x i8], i8, [1025 x i8] } zeroinitializer, [1025 x i8] %0, 0 - %5 = insertvalue { [1025 x i8], i8, [1025 x i8] } %4, i8 %1, 1 - %6 = insertvalue { [1025 x i8], i8, [1025 x i8] } %5, [1025 x i8] %3, 2 - ret { [1025 x i8], i8, [1025 x i8] } %6 + %stackalloc = alloca i8, align 1 + %call.result = 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 %call.result, ptr nonnull %stackalloc, ptr undef) #9 + call void @main.makeLargeValue(ptr nonnull %call.result, i8 %value, ptr undef) + %0 = add i8 %value, 1 + %1 = add i8 %value, 2 + %call.result1 = 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 %call.result1, ptr nonnull %stackalloc, ptr undef) #9 + call void @main.makeLargeValue(ptr nonnull %call.result1, i8 %1, ptr undef) + call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %return, ptr noundef nonnull align 1 dereferenceable(1025) %call.result, i32 1025, i1 false) + %2 = getelementptr inbounds nuw i8, ptr %return, i32 1025 + store i8 %0, ptr %2, align 1 + %3 = getelementptr inbounds nuw i8, ptr %return, i32 1026 + call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %3, ptr noundef nonnull align 1 dereferenceable(1025) %call.result1, i32 1025, i1 false) + ret void } ; Function Attrs: nounwind -define hidden [1025 x i8] @main.chooseLargeValue(i1 %flag, ptr %context) unnamed_addr #1 { +define hidden void @main.chooseLargeValue(ptr dereferenceable_or_null(1025) %return, i1 %flag, ptr %context) unnamed_addr #1 { entry: - %0 = call [1025 x i8] @main.makeLargeValue(i8 1, ptr undef) + %stackalloc = alloca i8, align 1 + %call.result = 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 %call.result, ptr nonnull %stackalloc, ptr undef) #9 + call void @main.makeLargeValue(ptr nonnull %call.result, i8 1, ptr undef) br i1 %flag, label %if.then, label %if.done if.then: ; preds = %entry - %1 = call [1025 x i8] @main.makeLargeValue(i8 42, ptr undef) + %call.result1 = 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 %call.result1, ptr nonnull %stackalloc, ptr undef) #9 + call void @main.makeLargeValue(ptr nonnull %call.result1, i8 42, ptr undef) br label %if.done if.done: ; preds = %if.then, %entry - %2 = phi [1025 x i8] [ %0, %entry ], [ %1, %if.then ] - ret [1025 x i8] %2 + %0 = phi ptr [ %call.result, %entry ], [ %call.result1, %if.then ] + call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %return, ptr noundef nonnull align 1 dereferenceable(1025) %0, i32 1025, i1 false) + ret void } ; Function Attrs: nounwind -define hidden %main.largeStruct @main.makePointerLargeValue(ptr dereferenceable_or_null(1) %value, ptr %context) unnamed_addr #1 { +define hidden void @main.makePointerLargeValue(ptr dereferenceable_or_null(1032) %return, ptr dereferenceable_or_null(1) %value, ptr %context) unnamed_addr #1 { entry: %stackalloc = alloca i8, align 1 - %complit = call align 4 dereferenceable(1032) ptr @runtime.alloc(i32 1032, ptr nonnull @"runtime/gc.layout:258-000000000000000000000000000000000000000000000000000000000000000002", ptr undef) #7 - call void @runtime.trackPointer(ptr nonnull %complit, ptr nonnull %stackalloc, ptr undef) #7 + %complit = call align 4 dereferenceable(1032) ptr @runtime.alloc(i32 1032, ptr nonnull @"runtime/gc.layout:258-000000000000000000000000000000000000000000000000000000000000000002", ptr undef) #9 + call void @runtime.trackPointer(ptr nonnull %complit, ptr nonnull %stackalloc, ptr undef) #9 br i1 false, label %store.throw, label %store.next store.next: ; preds = %entry %0 = getelementptr inbounds nuw i8, ptr %complit, i32 1028 store ptr %value, ptr %0, align 4 - %1 = load %main.largeStruct, ptr %complit, align 4 - %2 = extractvalue %main.largeStruct %1, 1 - call void @runtime.trackPointer(ptr %2, ptr nonnull %stackalloc, ptr undef) #7 - ret %main.largeStruct %1 + %1 = call align 4 dereferenceable(1032) ptr @runtime.alloc(i32 1032, ptr nonnull @"runtime/gc.layout:258-000000000000000000000000000000000000000000000000000000000000000002", ptr undef) #9 + call void @runtime.trackPointer(ptr nonnull %1, ptr nonnull %stackalloc, ptr undef) #9 + call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 4 dereferenceable(1032) %1, ptr noundef nonnull align 4 dereferenceable(1032) %complit, i32 1032, i1 false) + call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1032) %return, ptr noundef nonnull align 4 dereferenceable(1032) %1, i32 1032, i1 false) + ret void store.throw: ; preds = %entry unreachable @@ -264,18 +311,25 @@ store.throw: ; preds = %entry define hidden i8 @main.assertLargeValue(ptr %value.typecode, ptr %value.value, ptr %context) unnamed_addr #1 { entry: %stackalloc = alloca i8, align 1 - %large = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #7 - call void @runtime.trackPointer(ptr nonnull %large, ptr nonnull %stackalloc, ptr undef) #7 - %typecode = call i1 @runtime.typeAssert(ptr %value.typecode, ptr nonnull @"reflect/types.typeid:named:main.largeValue", ptr undef) #7 + %large = 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 %large, ptr nonnull %stackalloc, ptr undef) #9 + %typecode = call i1 @runtime.typeAssert(ptr %value.typecode, ptr nonnull @"reflect/types.typeid:named:main.largeValue", ptr undef) #9 + %typeassert.result = call align 1 dereferenceable(1026) ptr @runtime.alloc(i32 1026, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 + call void @runtime.trackPointer(ptr nonnull %typeassert.result, ptr nonnull %stackalloc, ptr undef) #9 + call void @llvm.memset.p0.i32(ptr noundef nonnull align 1 dereferenceable(1026) %typeassert.result, i8 0, i32 1026, i1 false) br i1 %typecode, label %typeassert.ok, label %typeassert.next typeassert.next: ; preds = %typeassert.ok, %entry - %typeassert.value = phi [1025 x i8] [ zeroinitializer, %entry ], [ %0, %typeassert.ok ] - store [1025 x i8] %typeassert.value, ptr %large, align 1 + %0 = getelementptr inbounds nuw i8, ptr %typeassert.result, i32 1025 + store i1 %typecode, ptr %0, align 1 + %t2 = 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 %t2, ptr nonnull %stackalloc, ptr undef) #9 + call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %t2, ptr noundef nonnull align 1 dereferenceable(1025) %typeassert.result, i32 1025, i1 false) + call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %large, ptr noundef nonnull align 1 dereferenceable(1025) %t2, i32 1025, i1 false) br i1 %typecode, label %if.done, label %if.then typeassert.ok: ; preds = %entry - %0 = load [1025 x i8], ptr %value.value, align 1 + call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %typeassert.result, ptr noundef nonnull align 1 dereferenceable(1025) %value.value, i32 1025, i1 false) br label %typeassert.next if.done: ; preds = %typeassert.next @@ -289,39 +343,35 @@ if.then: ; preds = %typeassert.next declare i1 @runtime.typeAssert(ptr, ptr dereferenceable_or_null(1), ptr) #0 +; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: write) +declare void @llvm.memset.p0.i32(ptr writeonly captures(none), i8, i32, i1 immarg) #7 + ; Function Attrs: nounwind -define hidden i8 @main.useLargeMap([1025 x i8] %key, [1025 x i8] %value, ptr %context) unnamed_addr #1 { +define hidden i8 @main.useLargeMap(ptr readonly dereferenceable_or_null(1025) %key, ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 { entry: - %hashmap.key2 = alloca [1025 x i8], align 1 - %hashmap.value1 = alloca [1025 x i8], align 1 - %hashmap.key = alloca [1025 x i8], align 1 - %hashmap.value = alloca [1025 x i8], align 1 %stackalloc = alloca i8, align 1 - %0 = call ptr @runtime.hashmapMakeGeneric(i32 1025, i32 1025, i32 1, ptr null, ptr nonnull @runtime.hash32, ptr null, ptr nonnull @runtime.memequal, ptr undef) #7 - call void @runtime.trackPointer(ptr %0, ptr nonnull %stackalloc, ptr undef) #7 - call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.value) - store [1025 x i8] %value, ptr %hashmap.value, align 1 - call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.key) - store [1025 x i8] %key, ptr %hashmap.key, align 1 - call void @runtime.hashmapBinarySet(ptr %0, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, ptr undef) #7 - call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.key) - call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.value) - %result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #7 - call void @runtime.trackPointer(ptr nonnull %result, ptr nonnull %stackalloc, ptr undef) #7 - call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.value1) - call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.key2) - store [1025 x i8] %key, ptr %hashmap.key2, align 1 - %1 = call i1 @runtime.hashmapBinaryGet(ptr %0, ptr nonnull %hashmap.key2, ptr nonnull %hashmap.value1, i32 1025, ptr undef) #7 - call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.key2) - %2 = load [1025 x i8], ptr %hashmap.value1, align 1 - call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.value1) - store [1025 x i8] %2, ptr %result, align 1 - br i1 %1, label %if.done, label %if.then + %0 = call ptr @runtime.hashmapMakeGeneric(i32 1025, i32 1025, i32 1, ptr null, ptr nonnull @runtime.hash32, ptr null, ptr nonnull @runtime.memequal, ptr undef) #9 + call void @runtime.trackPointer(ptr %0, ptr nonnull %stackalloc, ptr undef) #9 + call void @runtime.hashmapBinarySet(ptr %0, ptr %key, ptr %value, ptr undef) #9 + %result = 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 %result, ptr nonnull %stackalloc, ptr undef) #9 + %hashmap.result = call align 1 dereferenceable(1026) ptr @runtime.alloc(i32 1026, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 + call void @runtime.trackPointer(ptr nonnull %hashmap.result, ptr nonnull %stackalloc, ptr undef) #9 + %1 = call i1 @runtime.hashmapBinaryGet(ptr %0, ptr %key, ptr nonnull %hashmap.result, i32 1025, ptr undef) #9 + %2 = getelementptr inbounds nuw i8, ptr %hashmap.result, i32 1025 + store i1 %1, ptr %2, align 1 + %t3 = 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 %t3, ptr nonnull %stackalloc, ptr undef) #9 + call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %t3, ptr noundef nonnull align 1 dereferenceable(1025) %hashmap.result, i32 1025, i1 false) + call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %result, ptr noundef nonnull align 1 dereferenceable(1025) %t3, i32 1025, i1 false) + %3 = getelementptr inbounds nuw i8, ptr %hashmap.result, i32 1025 + %t4 = load i1, ptr %3, align 1 + br i1 %t4, label %if.done, label %if.then if.done: ; preds = %entry - %3 = getelementptr inbounds nuw i8, ptr %result, i32 1024 - %4 = load i8, ptr %3, align 1 - ret i8 %4 + %4 = getelementptr inbounds nuw i8, ptr %result, i32 1024 + %5 = load i8, ptr %4, align 1 + ret i8 %5 if.then: ; preds = %entry ret i8 0 @@ -333,77 +383,76 @@ declare i1 @runtime.memequal(ptr, ptr, i32, ptr) #0 declare ptr @runtime.hashmapMakeGeneric(i32, i32, i32, ptr, ptr, ptr, ptr, ptr) #0 -; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) -declare void @llvm.lifetime.start.p0(ptr captures(none)) #6 - declare void @runtime.hashmapBinarySet(ptr dereferenceable_or_null(48), ptr, ptr, ptr) #0 -; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) -declare void @llvm.lifetime.end.p0(ptr captures(none)) #6 - declare i1 @runtime.hashmapBinaryGet(ptr dereferenceable_or_null(48), ptr, ptr, i32, ptr) #0 ; Function Attrs: nounwind -define hidden i8 @main.useLargeChannel(ptr dereferenceable_or_null(36) %ch, [1025 x i8] %value, ptr %context) unnamed_addr #1 { +define hidden i8 @main.useLargeChannel(ptr dereferenceable_or_null(36) %ch, ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 { entry: - %chan.op2 = alloca %runtime.channelOp, align 8 - %chan.value1 = alloca [1025 x i8], align 1 + %chan.op1 = alloca %runtime.channelOp, align 8 %chan.op = alloca %runtime.channelOp, align 8 - %chan.value = alloca [1025 x i8], align 1 %stackalloc = alloca i8, align 1 - call void @llvm.lifetime.start.p0(ptr nonnull %chan.value) - store [1025 x i8] %value, ptr %chan.value, align 1 call void @llvm.lifetime.start.p0(ptr nonnull %chan.op) - call void @runtime.chanSend(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.op, ptr undef) #7 + call void @runtime.chanSend(ptr %ch, ptr %value, ptr nonnull %chan.op, ptr undef) #9 call void @llvm.lifetime.end.p0(ptr nonnull %chan.op) - call void @llvm.lifetime.end.p0(ptr nonnull %chan.value) - %result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #7 - call void @runtime.trackPointer(ptr nonnull %result, ptr nonnull %stackalloc, ptr undef) #7 - call void @llvm.lifetime.start.p0(ptr nonnull %chan.value1) - call void @llvm.lifetime.start.p0(ptr nonnull %chan.op2) - %0 = call i1 @runtime.chanRecv(ptr %ch, ptr nonnull %chan.value1, ptr nonnull %chan.op2, ptr undef) #7 - %chan.received = load [1025 x i8], ptr %chan.value1, align 1 - call void @llvm.lifetime.end.p0(ptr nonnull %chan.value1) - call void @llvm.lifetime.end.p0(ptr nonnull %chan.op2) - store [1025 x i8] %chan.received, ptr %result, align 1 - br i1 %0, label %if.done, label %if.then + %result = 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 %result, ptr nonnull %stackalloc, ptr undef) #9 + %chan.result = call align 1 dereferenceable(1026) ptr @runtime.alloc(i32 1026, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 + call void @runtime.trackPointer(ptr nonnull %chan.result, ptr nonnull %stackalloc, ptr undef) #9 + call void @llvm.lifetime.start.p0(ptr nonnull %chan.op1) + %0 = call i1 @runtime.chanRecv(ptr %ch, ptr nonnull %chan.result, ptr nonnull %chan.op1, ptr undef) #9 + %1 = getelementptr inbounds nuw i8, ptr %chan.result, i32 1025 + store i1 %0, ptr %1, align 1 + call void @llvm.lifetime.end.p0(ptr nonnull %chan.op1) + %t2 = 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 %t2, ptr nonnull %stackalloc, ptr undef) #9 + call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %t2, ptr noundef nonnull align 1 dereferenceable(1025) %chan.result, i32 1025, i1 false) + call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %result, ptr noundef nonnull align 1 dereferenceable(1025) %t2, i32 1025, i1 false) + %2 = getelementptr inbounds nuw i8, ptr %chan.result, i32 1025 + %t3 = load i1, ptr %2, align 1 + br i1 %t3, label %if.done, label %if.then if.done: ; preds = %entry - %1 = getelementptr inbounds nuw i8, ptr %result, i32 1024 - %2 = load i8, ptr %1, align 1 - ret i8 %2 + %3 = getelementptr inbounds nuw i8, ptr %result, i32 1024 + %4 = load i8, ptr %3, align 1 + ret i8 %4 if.then: ; preds = %entry ret i8 0 } +; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) +declare void @llvm.lifetime.start.p0(ptr captures(none)) #8 + declare void @runtime.chanSend(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #0 +; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) +declare void @llvm.lifetime.end.p0(ptr captures(none)) #8 + declare i1 @runtime.chanRecv(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #0 ; Function Attrs: nounwind -define hidden i8 @main.selectLargeChannel(ptr dereferenceable_or_null(36) %ch, [1025 x i8] %value, ptr %context) unnamed_addr #1 { +define hidden i8 @main.selectLargeChannel(ptr dereferenceable_or_null(36) %ch, ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 { entry: %select.block.alloca = alloca [2 x %runtime.channelOp], align 8 %select.states.alloca = alloca [2 x %runtime.chanSelectState], align 8 %select.recvbuf.alloca = alloca [1025 x i8], align 1 - %select.send.value = alloca [1025 x i8], align 1 %stackalloc = alloca i8, align 1 - store [1025 x i8] %value, ptr %select.send.value, align 1 call void @llvm.lifetime.start.p0(ptr nonnull %select.recvbuf.alloca) call void @llvm.lifetime.start.p0(ptr nonnull %select.states.alloca) store ptr %ch, ptr %select.states.alloca, align 4 %select.states.alloca.repack3 = getelementptr inbounds nuw i8, ptr %select.states.alloca, i32 4 - store ptr %select.send.value, ptr %select.states.alloca.repack3, align 4 + store ptr %value, ptr %select.states.alloca.repack3, align 4 %0 = getelementptr inbounds nuw i8, ptr %select.states.alloca, i32 8 store ptr %ch, ptr %0, align 4 %.repack5 = getelementptr inbounds nuw i8, ptr %select.states.alloca, i32 12 store ptr null, ptr %.repack5, align 4 call void @llvm.lifetime.start.p0(ptr nonnull %select.block.alloca) - %select.result = call { i32, i1 } @runtime.chanSelect(ptr nonnull %select.recvbuf.alloca, ptr nonnull %select.states.alloca, i32 2, i32 2, ptr nonnull %select.block.alloca, i32 2, i32 2, ptr undef) #7 + %select.result = call { i32, i1 } @runtime.chanSelect(ptr nonnull %select.recvbuf.alloca, ptr nonnull %select.states.alloca, i32 2, i32 2, ptr nonnull %select.block.alloca, i32 2, i32 2, ptr undef) #9 call void @llvm.lifetime.end.p0(ptr nonnull %select.block.alloca) call void @llvm.lifetime.end.p0(ptr nonnull %select.states.alloca) - call void @runtime.trackPointer(ptr nonnull %select.recvbuf.alloca, ptr nonnull %stackalloc, ptr undef) #7 + call void @runtime.trackPointer(ptr nonnull %select.recvbuf.alloca, ptr nonnull %stackalloc, ptr undef) #9 %1 = extractvalue { i32, i1 } %select.result, 0 %2 = icmp eq i32 %1, 0 br i1 %2, label %select.body, label %select.next @@ -416,18 +465,20 @@ select.next: ; preds = %entry br i1 %3, label %select.body1, label %select.next2 select.body1: ; preds = %select.next - %result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #7 - call void @runtime.trackPointer(ptr nonnull %result, ptr nonnull %stackalloc, ptr undef) #7 - %select.received = load [1025 x i8], ptr %select.recvbuf.alloca, align 1 - store [1025 x i8] %select.received, ptr %result, align 1 + %result = 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 %result, ptr nonnull %stackalloc, ptr undef) #9 + %select.received = 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 %select.received, ptr nonnull %stackalloc, ptr undef) #9 + call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %select.received, ptr noundef nonnull align 1 dereferenceable(1025) %select.recvbuf.alloca, i32 1025, i1 false) + call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %result, ptr noundef nonnull align 1 dereferenceable(1025) %select.received, i32 1025, i1 false) %4 = getelementptr inbounds nuw i8, ptr %result, i32 1024 %5 = load i8, ptr %4, align 1 ret i8 %5 select.next2: ; preds = %select.next - call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:basic:string", ptr nonnull %stackalloc, ptr undef) #7 - call void @runtime.trackPointer(ptr nonnull @"main$pack", ptr nonnull %stackalloc, ptr undef) #7 - call void @runtime._panic(ptr nonnull @"reflect/types.type:basic:string", ptr nonnull @"main$pack", ptr undef) #7 + call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:basic:string", ptr nonnull %stackalloc, ptr undef) #9 + call void @runtime.trackPointer(ptr nonnull @"main$pack", ptr nonnull %stackalloc, ptr undef) #9 + call void @runtime._panic(ptr nonnull @"reflect/types.type:basic:string", ptr nonnull @"main$pack", ptr undef) #9 unreachable } @@ -437,9 +488,11 @@ declare void @runtime._panic(ptr, ptr, ptr) #0 attributes #0 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #1 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } -attributes #2 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } -attributes #3 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="main.$methods.makeLargeValue:func:{}{named:main.largeValue}" "tinygo-methods"="main.$methods.makeLargeValue:func:{}{named:main.largeValue}; main.$methods.readLargeValue:func:{named:main.largeValue}{basic:uint8}" } -attributes #4 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="main.$methods.readLargeValue:func:{named:main.largeValue}{basic:uint8}" "tinygo-methods"="main.$methods.makeLargeValue:func:{}{named:main.largeValue}; main.$methods.readLargeValue:func:{named:main.largeValue}{basic:uint8}" } -attributes #5 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper"="main.readLargeValue" } -attributes #6 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) } -attributes #7 = { nounwind } +attributes #2 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) } +attributes #3 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #4 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-indirect-result"="true" "tinygo-invoke"="main.$methods.makeLargeValue:func:{}{named:main.largeValue}" "tinygo-methods"="main.$methods.makeLargeValue:func:{}{named:main.largeValue}; main.$methods.readLargeValue:func:{named:main.largeValue}{basic:uint8}" } +attributes #5 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="main.$methods.readLargeValue:func:{named:main.largeValue}{basic:uint8}" "tinygo-methods"="main.$methods.makeLargeValue:func:{}{named:main.largeValue}; main.$methods.readLargeValue:func:{named:main.largeValue}{basic:uint8}" } +attributes #6 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper"="main.readLargeValue" } +attributes #7 = { nocallback nofree nounwind willreturn memory(argmem: write) } +attributes #8 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) } +attributes #9 = { nounwind } diff --git a/transform/interface-lowering.go b/transform/interface-lowering.go index 6e5af1559..9c1adf724 100644 --- a/transform/interface-lowering.go +++ b/transform/interface-lowering.go @@ -517,6 +517,10 @@ func (p *lowerInterfacesPass) defineInterfaceMethodFunc(fn llvm.Value, itf *inte context := fn.LastParam() actualType := llvm.PrevParam(context) returnType := fn.GlobalValueType().ReturnType() + resultOffset := 0 + if fn.GetStringAttributeAtIndex(-1, "tinygo-indirect-result").GetStringValue() == "true" { + resultOffset = 1 + } context.SetName("context") actualType.SetName("actualType") fn.SetLinkage(llvm.InternalLinkage) @@ -526,9 +530,9 @@ func (p *lowerInterfacesPass) defineInterfaceMethodFunc(fn llvm.Value, itf *inte // Collect the params that will be passed to the functions to call. // These params exclude the receiver (which may actually consist of multiple // parts). - params := make([]llvm.Value, fn.ParamsCount()-3) + params := make([]llvm.Value, fn.ParamsCount()-3-resultOffset) for i := range params { - params[i] = fn.Param(i + 1) + params[i] = fn.Param(i + 1 + resultOffset) } params = append(params, llvm.Undef(p.ptrType), @@ -570,14 +574,20 @@ func (p *lowerInterfacesPass) defineInterfaceMethodFunc(fn llvm.Value, itf *inte function := typ.getMethod(signature).function p.builder.SetInsertPointAtEnd(bb) - receiver := fn.FirstParam() + receiver := fn.Param(resultOffset) - paramTypes := []llvm.Type{receiver.Type()} - for _, param := range params { - paramTypes = append(paramTypes, param.Type()) + callParams := make([]llvm.Value, 0, len(params)+2+resultOffset) + if resultOffset != 0 { + callParams = append(callParams, fn.FirstParam()) + } + callParams = append(callParams, receiver) + callParams = append(callParams, params...) + paramTypes := make([]llvm.Type, len(callParams)) + for i, param := range callParams { + paramTypes[i] = param.Type() } functionType := llvm.FunctionType(returnType, paramTypes, false) - retval := p.builder.CreateCall(functionType, function, append([]llvm.Value{receiver}, params...), "") + retval := p.builder.CreateCall(functionType, function, callParams, "") if retval.Type().TypeKind() == llvm.VoidTypeKind { p.builder.CreateRetVoid() } else {