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 {