diff --git a/GNUmakefile b/GNUmakefile index fb300a566b..321eaca77b 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 08952c4db6..257973320e 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,18 @@ 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) +} + +func (c *compilerContext) expandDirectFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo { switch t.TypeKind() { case llvm.StructTypeKind: fieldInfos := c.flattenAggregateType(t, name, goType) @@ -115,6 +130,38 @@ func (c *compilerContext) expandFormalParamType(t llvm.Type, name string, goType 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 82139de8ba..a562e97e3a 100644 --- a/compiler/channel.go +++ b/compiler/channel.go @@ -30,17 +30,15 @@ func (b *builder) createMakeChan(expr *ssa.MakeChan) llvm.Value { // actual channel send operation during goroutine lowering. func (b *builder) createChanSend(instr *ssa.Send) { ch := b.getValue(instr.Chan, getPos(instr)) - chanValue := b.getValue(instr.X, getPos(instr)) // 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.createTemporaryAlloca(valueType, "chan.value") - b.CreateStore(chanValue, valueAlloca) + storage = b.getValueStorage(instr.X, "chan.value") } // Allocate buffer for the channel operation. @@ -48,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 @@ -66,37 +62,17 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value { ch := b.getValue(unop.X, getPos(unop)) // Allocate memory to receive into. - isZeroSize := b.targetData.TypeAllocSize(valueType) == 0 - var valueAlloca, valueAllocaSize llvm.Value - if isZeroSize { - valueAlloca = llvm.ConstNull(b.dataPtrType) - } else { - valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value") - } + result := b.createRuntimeValueResult(valueType, unop.CommaOk, true, "chan") // Allocate buffer for the channel operation. channelOp := b.getLLVMRuntimeType("channelOp") channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op") // Do the receive. - commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "") - var received llvm.Value - if isZeroSize { - received = llvm.ConstNull(valueType) - } else { - received = b.CreateLoad(valueType, valueAlloca, "chan.received") - b.emitLifetimeEnd(valueAlloca, valueAllocaSize) - } + commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, result.valuePtr, channelOpAlloca}, "") + received := result.finish(b, commaOk, "chan.received") b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize) - - if unop.CommaOk { - tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false)) - tuple = b.CreateInsertValue(tuple, received, 0, "") - tuple = b.CreateInsertValue(tuple, commaOk, 1, "") - return tuple - } else { - return received - } + return received } // createChanClose closes the given channel. @@ -170,9 +146,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value { case types.SendOnly: // Store this value in an alloca and put a pointer to this alloca // in the send state. - sendValue := b.getValue(state.Send, state.Pos) - alloca := llvmutil.CreateEntryBlockAlloca(b.Builder, sendValue.Type(), "select.send.value") - b.CreateStore(sendValue, alloca) + alloca := b.getSelectSendStorage(state.Send) selectState = b.CreateInsertValue(selectState, alloca, 1, "") default: panic("unreachable") @@ -280,7 +254,17 @@ func (b *builder) getChanSelectResult(expr *ssa.Extract) llvm.Value { // receive can proceed at a time) so we'll get that alloca, bitcast // it to the correct type, and dereference it. recvbuf := b.selectRecvBuf[expr.Tuple.(*ssa.Select)] - typ := b.getLLVMType(expr.Type()) - return b.CreateLoad(typ, recvbuf, "") + return b.loadFromStorage(recvbuf, expr.Type(), "select.received") + } +} + +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) + return ptr } diff --git a/compiler/compiler.go b/compiler/compiler.go index 8cbc1a919a..46ad506a54 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) @@ -1523,10 +1547,7 @@ func (b *builder) createInstruction(instr ssa.Instruction) { b.diagnostics = append(b.diagnostics, err) b.locals[instr] = llvm.Undef(b.getLLVMType(instr.Type())) } else { - b.locals[instr] = value - if len(*instr.Referrers()) != 0 && b.NeedsStackObjects { - b.trackExpr(instr, value) - } + b.setValue(instr, value) } case *ssa.DebugRef: // ignore @@ -1546,10 +1567,8 @@ func (b *builder) createInstruction(instr ssa.Instruction) { b.CreateBr(blockJump) case *ssa.MapUpdate: m := b.getValue(instr.Map, getPos(instr)) - key := b.getValue(instr.Key, getPos(instr)) - value := b.getValue(instr.Value, getPos(instr)) mapType := instr.Map.Type().Underlying().(*types.Map) - b.createMapUpdate(mapType.Key(), m, key, value, instr.Pos()) + b.createMapUpdate(mapType.Key(), m, instr.Key, instr.Value, instr.Pos()) case *ssa.Panic: value := b.getValue(instr.X, getPos(instr)) b.createRuntimeInvoke("_panic", []llvm.Value{value}, "") @@ -1558,19 +1577,7 @@ func (b *builder) createInstruction(instr ssa.Instruction) { if b.hasDeferFrame() { b.createRuntimeCall("destroyDeferFrame", []llvm.Value{b.deferFrame}, "") } - if len(instr.Results) == 0 { - b.CreateRetVoid() - } else if len(instr.Results) == 1 { - b.CreateRet(b.getValue(instr.Results[0], getPos(instr))) - } else { - // Multiple return values. Put them all in a struct. - retVal := llvm.ConstNull(b.llvmFn.GlobalValueType().ReturnType()) - for i, result := range instr.Results { - val := b.getValue(result, getPos(instr)) - retVal = b.CreateInsertValue(retVal, val, i, "") - } - b.CreateRet(retVal) - } + b.createReturn(instr.Results, getPos(instr)) case *ssa.RunDefers: // Note where we're going to put the rundefers block run := b.insertBasicBlock("rundefers.block") @@ -1584,18 +1591,246 @@ func (b *builder) createInstruction(instr ssa.Instruction) { b.createChanSend(instr) case *ssa.Store: llvmAddr := b.getValue(instr.Addr, getPos(instr)) - llvmVal := b.getValue(instr.Val, getPos(instr)) b.createNilCheck(instr.Addr, llvmAddr, "store") - if b.targetData.TypeAllocSize(llvmVal.Type()) == 0 { + llvmType := b.getLLVMType(instr.Val.Type()) + if b.targetData.TypeAllocSize(llvmType) == 0 { // nothing to store return } - b.CreateStore(llvmVal, llvmAddr) + b.storeValue(llvmAddr, instr.Val) default: b.addError(instr.Pos(), "unknown instruction: "+instr.String()) } } +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) + } +} + +func (b *builder) createReturn(results []ssa.Value, pos token.Pos) { + if len(results) == 0 { + b.CreateRetVoid() + } 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)) + } else { + result := llvm.ConstNull(b.llvmFn.GlobalValueType().ReturnType()) + for i, value := range results { + result = b.CreateInsertValue(result, b.getValue(value, pos), i, "") + } + b.CreateRet(result) + } +} + +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) { + 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 { + llvmType := b.getLLVMType(typ) + if b.isIndirectAggregate(llvmType) { + return b.copyToIndirectStorage(ptr, llvmType, name) + } + return b.CreateLoad(llvmType, ptr, name) +} + +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()) + if b.isIndirectAggregate(typ) { + return valueStorage{ptr: b.getValuePointer(value)} + } + ptr, size := b.createTemporaryAlloca(typ, name) + b.storeValue(ptr, value) + 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 + zero bool + commaOk bool +} + +func (b *builder) createRuntimeValueResult(valueType llvm.Type, commaOk, zeroAsNull bool, name string) runtimeValueResult { + 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 + return result + } + result.valuePtr, result.valueSize = b.createTemporaryAlloca(valueType, name+".value") + result.temporary = true + return result +} + +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) + } else { + value = b.CreateLoad(r.valueType, r.valuePtr, name) + } + if r.temporary { + b.emitLifetimeEnd(r.valuePtr, r.valueSize) + } + if !r.commaOk { + return value + } + result := llvm.Undef(r.resultType) + result = b.CreateInsertValue(result, value, 0, "") + return b.CreateInsertValue(result, commaOk, 1, "") +} + // createBuiltin lowers a builtin Go function (append, close, delete, etc.) to // LLVM IR. It uses runtime calls for some builtins. func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, callName string, pos token.Pos) (llvm.Value, error) { @@ -2030,14 +2265,10 @@ 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))) - } - // Try to call the function directly for trivially static calls. var callee, context llvm.Value var calleeType llvm.Type + var invokeTypecode, invokeReceiver llvm.Value exported := false if fn := instr.StaticCallee(); fn != nil { calleeType, callee = b.getFunction(fn) @@ -2067,19 +2298,18 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) exported = info.exported } else if call, ok := instr.Value.(*ssa.Builtin); ok { // Builtin function (append, close, delete, etc.).) + var params []llvm.Value var argTypes []types.Type for _, arg := range instr.Args { argTypes = append(argTypes, arg.Type()) + params = append(params, b.getValue(arg, getPos(instr))) } return b.createBuiltin(argTypes, params, call.Name(), instr.Pos()) } else if instr.IsInvoke() { // Interface method call (aka invoke call). itf := b.getValue(instr.Value, getPos(instr)) // interface value (runtime._interface) - typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode") - value := b.CreateExtractValue(itf, 1, "invoke.func.value") // receiver - // Prefix the params with receiver value and suffix with typecode. - params = append([]llvm.Value{value}, params...) - params = append(params, typecode) + invokeTypecode = b.CreateExtractValue(itf, 0, "invoke.func.typecode") + invokeReceiver = b.CreateExtractValue(itf, 1, "invoke.func.value") callee = b.getInvokeFunction(instr) calleeType = callee.GlobalValueType() context = llvm.Undef(b.dataPtrType) @@ -2093,7 +2323,23 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) b.createNilCheck(instr.Value, callee, "fpcall") } + var params []llvm.Value + for _, param := range instr.Args { + params = append(params, b.getCallArgument(param, exported)) + } + if instr.IsInvoke() { + params = append([]llvm.Value{invokeReceiver}, params...) + params = append(params, invokeTypecode) + } + 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) @@ -2132,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 { @@ -2214,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. @@ -2245,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 @@ -2270,11 +2532,11 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { case *ssa.Global: panic("global is not an expression") case *ssa.Index: - collection := b.getValue(expr.X, getPos(expr)) index := b.getValue(expr.Index, getPos(expr)) switch xType := expr.X.Type().Underlying().(type) { case *types.Basic: // extract byte from string + collection := b.getValue(expr.X, getPos(expr)) // Value type must be a string, which is a basic type. if xType.Info()&types.IsString == 0 { panic("lookup on non-string?") @@ -2308,13 +2570,12 @@ 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 := collection.Type() - alloca, allocaSize := b.createTemporaryAlloca(arrayType, "index.alloca") - b.CreateStore(collection, alloca) + arrayType := b.getLLVMType(expr.X.Type()) + 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") - result := b.CreateLoad(arrayType.ElementType(), ptr, "index.load") - b.emitLifetimeEnd(alloca, allocaSize) + ptr := b.CreateInBoundsGEP(arrayType, storage.ptr, []llvm.Value{zero, index}, "index.gep") + result := b.loadFromStorage(ptr, expr.Type(), "index.load") + b.endValueStorage(storage) return result, nil default: panic("unknown *ssa.Index type") @@ -2373,17 +2634,21 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { } case *ssa.Lookup: // map lookup value := b.getValue(expr.X, getPos(expr)) - index := b.getValue(expr.Index, getPos(expr)) valueType := expr.Type() if expr.CommaOk { valueType = valueType.(*types.Tuple).At(0).Type() } - return b.createMapLookup(expr.X.Type().Underlying().(*types.Map).Key(), valueType, value, index, expr.CommaOk, expr.Pos()) + return b.createMapLookup(expr.X.Type().Underlying().(*types.Map).Key(), valueType, value, expr.Index, expr.CommaOk, expr.Pos()) case *ssa.MakeChan: return b.createMakeChan(expr), nil 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: @@ -2450,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: @@ -3453,8 +3719,7 @@ func (b *builder) createUnOp(unop *ssa.UnOp) (llvm.Value, error) { return fn, nil } else { b.createNilCheck(unop.X, x, "deref") - load := b.CreateLoad(valueType, x, "") - return load, nil + return b.loadFromStorage(x, unop.Type(), ""), nil } case token.XOR: // ^x, toggle all bits in integer return b.CreateXor(x, llvm.ConstInt(x.Type(), ^uint64(0), false), ""), nil diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index e5d781849b..ad486989af 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -52,6 +52,7 @@ func TestCompiler(t *testing.T) { {"gc.go", "", ""}, {"zeromap.go", "", ""}, {"generics.go", "", ""}, + {"large.go", "", ""}, } if goMinor >= 20 { tests = append(tests, testCase{"go1.20.go", "", ""}) @@ -130,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) + } + } +} + // fuzzyEqualIR returns true if the two LLVM IR strings passed in are roughly // equal. That means, only relevant lines are compared (excluding comments // etc.). @@ -257,6 +298,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 4fd9715b52..ddde9eee10 100644 --- a/compiler/defer.go +++ b/compiler/defer.go @@ -359,6 +359,44 @@ type tarjanNode struct { cyclic bool } +type llvmValueList struct { + values []llvm.Value + types []llvm.Type +} + +func newLLVMValueList(values ...llvm.Value) llvmValueList { + var list llvmValueList + list.append(values...) + return list +} + +func (l *llvmValueList) append(values ...llvm.Value) { + for _, value := range values { + l.values = append(l.values, value) + l.types = append(l.types, value.Type()) + } +} + +func (l *llvmValueList) appendSSAValues(values []ssa.Value, lower func(ssa.Value) llvm.Value) { + for _, value := range values { + l.append(lower(value)) + } +} + +func (b *builder) loadDeferredCallParams(structType llvm.Type, ptr llvm.Value) []llvm.Value { + fieldTypes := structType.StructElementTypes() + values := make([]llvm.Value, 0, len(fieldTypes)-2) + zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) + for i := 2; i < len(fieldTypes); i++ { + fieldPtr := b.CreateInBoundsGEP(structType, ptr, []llvm.Value{ + zero, + llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false), + }, "gep") + values = append(values, b.CreateLoad(fieldTypes[i], fieldPtr, "param")) + } + return values +} + // createDefer emits a single defer instruction, to be run when this function // returns. func (b *builder) createDefer(instr *ssa.Defer) { @@ -366,8 +404,10 @@ func (b *builder) createDefer(instr *ssa.Defer) { // make a linked list. next := b.CreateLoad(b.dataPtrType, b.deferPtr, "defer.next") - var values []llvm.Value - valueTypes := []llvm.Type{b.uintptrType, next.Type()} + var values llvmValueList + lowerArgument := func(value ssa.Value) llvm.Value { + return b.getCallArgument(value, false) + } if instr.Call.IsInvoke() { // Method call on an interface. @@ -384,13 +424,8 @@ func (b *builder) createDefer(instr *ssa.Defer) { itf := b.getValue(instr.Call.Value, getPos(instr)) // interface typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode") receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver") - values = []llvm.Value{callback, next, typecode, receiverValue} - valueTypes = append(valueTypes, b.dataPtrType, b.dataPtrType) - for _, arg := range instr.Call.Args { - val := b.getValue(arg, getPos(instr)) - values = append(values, val) - valueTypes = append(valueTypes, val.Type()) - } + values = newLLVMValueList(callback, next, typecode, receiverValue) + values.appendSSAValues(instr.Call.Args, lowerArgument) } else if callee, ok := instr.Call.Value.(*ssa.Function); ok { // Regular function call. @@ -402,12 +437,11 @@ func (b *builder) createDefer(instr *ssa.Defer) { // Collect all values to be put in the struct (starting with // runtime._defer fields). - values = []llvm.Value{callback, next} - for _, param := range instr.Call.Args { - llvmParam := b.getValue(param, getPos(instr)) - values = append(values, llvmParam) - valueTypes = append(valueTypes, llvmParam.Type()) - } + values = newLLVMValueList(callback, next) + 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. @@ -430,14 +464,9 @@ func (b *builder) createDefer(instr *ssa.Defer) { // Collect all values to be put in the struct (starting with // runtime._defer fields, followed by all parameters including the // context pointer). - values = []llvm.Value{callback, next} - for _, param := range instr.Call.Args { - llvmParam := b.getValue(param, getPos(instr)) - values = append(values, llvmParam) - valueTypes = append(valueTypes, llvmParam.Type()) - } - values = append(values, context) - valueTypes = append(valueTypes, context.Type()) + values = newLLVMValueList(callback, next) + values.appendSSAValues(instr.Call.Args, lowerArgument) + values.append(context) } else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok { var argTypes []types.Type @@ -460,11 +489,8 @@ func (b *builder) createDefer(instr *ssa.Defer) { // Collect all values to be put in the struct (starting with // runtime._defer fields). - values = []llvm.Value{callback, next} - for _, param := range argValues { - values = append(values, param) - valueTypes = append(valueTypes, param.Type()) - } + values = newLLVMValueList(callback, next) + values.append(argValues...) } else { funcValue := b.getValue(instr.Call.Value, getPos(instr)) @@ -479,20 +505,15 @@ func (b *builder) createDefer(instr *ssa.Defer) { // Collect all values to be put in the struct (starting with // runtime._defer fields, followed by all parameters including the // context pointer). - values = []llvm.Value{callback, next, funcValue} - valueTypes = append(valueTypes, funcValue.Type()) - for _, param := range instr.Call.Args { - llvmParam := b.getValue(param, getPos(instr)) - values = append(values, llvmParam) - valueTypes = append(valueTypes, llvmParam.Type()) - } + values = newLLVMValueList(callback, next, funcValue) + values.appendSSAValues(instr.Call.Args, lowerArgument) } // Make a struct out of the collected values to put in the deferred call // struct. - deferredCallType := b.ctx.StructType(valueTypes, false) + deferredCallType := b.ctx.StructType(values.types, false) deferredCall := llvm.ConstNull(deferredCallType) - for i, value := range values { + for i, value := range values.values { deferredCall = b.CreateInsertValue(deferredCall, value, i, "") } @@ -594,19 +615,11 @@ 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). - forwardParams := []llvm.Value{} - zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) deferredCallType := b.ctx.StructType(valueTypes, false) - for i := 2; i < len(valueTypes); i++ { - gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "gep") - forwardParam := b.CreateLoad(valueTypes[i], gep, "param") - forwardParams = append(forwardParams, forwardParam) - } + forwardParams := b.loadDeferredCallParams(deferredCallType, deferData) var fnPtr llvm.Value var fnType llvm.Type @@ -635,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, "") @@ -643,27 +657,21 @@ 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. - forwardParams := []llvm.Value{} - zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) - for i := range getParams(callback.Signature) { - gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep") - forwardParam := b.CreateLoad(valueTypes[i+2], gep, "param") - forwardParams = append(forwardParams, forwardParam) - } + forwardParams := b.loadDeferredCallParams(deferredCallType, deferData) // 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) @@ -673,24 +681,16 @@ 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) // Extract the params from the struct. - forwardParams := []llvm.Value{} - zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) - for i := 2; i < len(valueTypes); i++ { - gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "") - forwardParam := b.CreateLoad(valueTypes[i], gep, "param") - forwardParams = append(forwardParams, forwardParam) - } + forwardParams := b.loadDeferredCallParams(deferredCallType, deferData) // 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] @@ -707,13 +707,7 @@ func (b *builder) createRunDefers() { deferredCallType := b.ctx.StructType(valueTypes, false) // Extract the params from the struct. - var argValues []llvm.Value - zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) - for i := 0; i < params.Len(); i++ { - gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep") - forwardParam := b.CreateLoad(valueTypes[i+2], gep, "param") - argValues = append(argValues, forwardParam) - } + argValues := b.loadDeferredCallParams(deferredCallType, deferData) _, err := b.createBuiltin(db.argTypes, argValues, db.callName, db.pos) if err != nil { diff --git a/compiler/func.go b/compiler/func.go index ed5808c72f..260ff07da0 100644 --- a/compiler/func.go +++ b/compiler/func.go @@ -10,6 +10,91 @@ 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: + return c.ctx.VoidType() + case 1: + return c.getLLVMType(sig.Results().At(0).Type()) + default: + results := make([]llvm.Type, sig.Results().Len()) + for i := range results { + results[i] = c.getLLVMType(sig.Results().At(i).Type()) + } + return c.ctx.StructType(results, false) + } +} + +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 { @@ -48,28 +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 { - // Get the return type. - var returnType llvm.Type - switch typ.Results().Len() { - case 0: - // No return values. - returnType = c.ctx.VoidType() - case 1: - // Just one return value. - returnType = c.getLLVMType(typ.Results().At(0).Type()) - default: - // Multiple return values. Put them together in a struct. - // This appears to be the common way to handle multiple return values in - // LLVM. - members := make([]llvm.Type, typ.Results().Len()) - for i := 0; i < typ.Results().Len(); i++ { - members[i] = c.getLLVMType(typ.Results().At(i).Type()) - } - returnType = c.ctx.StructType(members, false) - } + 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 b3e02c2002..26d489a3a4 100644 --- a/compiler/goroutine.go +++ b/compiler/goroutine.go @@ -47,20 +47,16 @@ func (b *builder) createGo(instr *ssa.Go) { return } - // Get all function parameters to pass to the goroutine. var params []llvm.Value - for _, param := range instr.Call.Args { - params = append(params, b.expandFormalParam(b.getValue(param, getPos(instr)))...) - } - var prefix string var funcPtr llvm.Value 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. - var context llvm.Value switch value := instr.Call.Value.(type) { case *ssa.Function: // Goroutine call is regular function call. No context is necessary. @@ -73,10 +69,10 @@ func (b *builder) createGo(instr *ssa.Go) { panic("StaticCallee returned an unexpected value") } if !context.IsNil() { - params = append(params, context) // context parameter 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)) @@ -84,22 +80,31 @@ func (b *builder) createGo(instr *ssa.Go) { itfValue := b.CreateExtractValue(itf, 1, "") funcPtr = b.getInvokeFunction(&instr.Call) funcType = funcPtr.GlobalValueType() - params = append([]llvm.Value{itfValue}, params...) // start with receiver - params = append(params, itfTypeCode) // end with typecode + params = append(params, itfValue) + context = itfTypeCode } else { // This is a function pointer. // At the moment, two extra params are passed to the newly started // goroutine: // * The function context, for closures. // * The function pointer (for tasks). - var context llvm.Value funcPtr, context = b.decodeFuncValue(b.getValue(instr.Call.Value, getPos(instr))) funcType = b.getLLVMFunctionType(instr.Call.Value.Type().Underlying().(*types.Signature)) - params = append(params, context, funcPtr) hasContext = true prefix = b.getFunctionInfo(b.fn).linkName } + for _, param := range instr.Call.Args { + params = append(params, b.getGoroutineCallArgument(param, exported)...) + } + if !context.IsNil() { + params = append(params, context) + } + 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 callee := b.createGoroutineStartWrapper(funcType, funcPtr, prefix, hasContext, false, instr.Pos()) @@ -122,6 +127,15 @@ 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, 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 // pragma. This wrapper function is quite complex when the scheduler is enabled: // it needs to start a new goroutine each time the exported function is called. diff --git a/compiler/interface.go b/compiler/interface.go index bb427e6e54..631a131f62 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 @@ -1207,6 +1233,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)) } @@ -1247,6 +1276,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) } @@ -1261,7 +1291,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) @@ -1287,8 +1323,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 4f9ec66ea5..3481ae90ac 100644 --- a/compiler/map.go +++ b/compiler/map.go @@ -72,19 +72,20 @@ func (b *builder) getRuntimeFunctionValue(name string, sig *types.Signature) llv // createMapLookup returns the value in a map. It calls a runtime function // depending on the map key type to load the map value and its comma-ok value. -func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) { +func (b *builder) createMapLookup(keyType, valueType types.Type, m llvm.Value, key ssa.Value, commaOk bool, pos token.Pos) (llvm.Value, error) { llvmValueType := b.getLLVMType(valueType) // Allocate the memory for the resulting type. Do not zero this memory: it // will be zeroed by the hashmap get implementation if the key is not // present in the map. - mapValueAlloca, mapValueAllocaSize := b.createTemporaryAlloca(llvmValueType, "hashmap.value") + result := b.createRuntimeValueResult(llvmValueType, commaOk, false, "hashmap") + mapValueAlloca := result.valuePtr // We need the map size (with type uintptr) to pass to the hashmap*Get // functions. This is necessary because those *Get functions are valid on // nil maps, and they'll need to zero the value pointer by that number of // bytes. - mapValueSize := mapValueAllocaSize + mapValueSize := result.valueSize if mapValueSize.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() { mapValueSize = llvm.ConstTrunc(mapValueSize, b.uintptrType) } @@ -94,60 +95,46 @@ func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Val keyType = keyType.Underlying() if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { // key is a string - params := []llvm.Value{m, key, mapValueAlloca, mapValueSize} + params := []llvm.Value{m, b.getValue(key, getPos(key)), mapValueAlloca, mapValueSize} commaOkValue = b.createRuntimeCall("hashmapStringGet", params, "") } else { // Key stored at actual type: either binary-comparable or with // compiler-generated hash/equal. - mapKeyAlloca, mapKeySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") - b.CreateStore(key, mapKeyAlloca) - 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) } - // Load the resulting value from the hashmap. The value is set to the zero - // value if the key doesn't exist in the hashmap. - mapValue := b.CreateLoad(llvmValueType, mapValueAlloca, "") - b.emitLifetimeEnd(mapValueAlloca, mapValueAllocaSize) - - if commaOk { - tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{llvmValueType, b.ctx.Int1Type()}, false)) - tuple = b.CreateInsertValue(tuple, mapValue, 0, "") - tuple = b.CreateInsertValue(tuple, commaOkValue, 1, "") - return tuple, nil - } else { - return mapValue, nil - } + // The value is set to the zero value if the key doesn't exist. + return result.finish(b, commaOkValue, ""), nil } // createMapUpdate updates a map key to a given value, by creating an // appropriate runtime call. -func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) { - valueAlloca, valueSize := b.createTemporaryAlloca(value.Type(), "hashmap.value") - b.CreateStore(value, valueAlloca) +func (b *builder) createMapUpdate(keyType types.Type, m llvm.Value, key, value ssa.Value, pos token.Pos) { + 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, 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.createTemporaryAlloca(key.Type(), "hashmap.key") - b.CreateStore(key, keyAlloca) + 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 90dfc0d08b..944f74240e 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -79,24 +79,27 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value) return llvmFn.GlobalValueType(), llvmFn } - var retType llvm.Type - if fn.Signature.Results() == nil { - retType = c.ctx.VoidType() - } else if fn.Signature.Results().Len() == 1 { - retType = c.getLLVMType(fn.Signature.Results().At(0).Type()) - } else { - results := make([]llvm.Type, 0, fn.Signature.Results().Len()) - for v := range fn.Signature.Results().Variables() { - results = append(results, c.getLLVMType(v.Type())) - } - retType = c.ctx.StructType(results, false) + 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 @@ -106,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 0000000000..91a0740fa2 --- /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.go b/compiler/testdata/large.go new file mode 100644 index 0000000000..5fc9cae847 --- /dev/null +++ b/compiler/testdata/large.go @@ -0,0 +1,120 @@ +package main + +type largeValue [1025]byte + +type largeStruct struct { + value [1025]byte + ptr *byte +} + +type largeInterface interface { + makeLargeValue() largeValue + readLargeValue(largeValue) byte +} + +type largeReceiver largeValue + +func makeLargeValue(value byte) largeValue { + var result largeValue + result[len(result)-1] = value + return result +} + +func makeZeroLargeValue() largeValue { + return largeValue{} +} + +func passZeroLargeValue() byte { + return readLargeValue(largeValue{}) +} + +func readLargeValue(value largeValue) byte { + return value[len(value)-1] +} + +func useLargeValue() byte { + return readLargeValue(makeLargeValue(42)) +} + +func useLargeFunctionValue(fn func(largeValue) byte) byte { + return fn(makeLargeValue(42)) +} + +func (receiver largeReceiver) makeLargeValue() largeValue { + return largeValue(receiver) +} + +func (receiver largeReceiver) readLargeValue(value largeValue) byte { + return value[len(value)-1] +} + +func useLargeInterface(value largeInterface) byte { + return value.readLargeValue(value.makeLargeValue()) +} + +func deferLargeValue(value largeValue) { + defer readLargeValue(value) +} + +func goLargeValue(value largeValue) { + go readLargeValue(value) +} + +func makeLargeResults(value byte) (largeValue, byte) { + return makeLargeValue(value), value +} + +func makeTwoLargeResults(value byte) (largeValue, largeValue) { + return makeLargeValue(value), makeLargeValue(value + 1) +} + +func makeMixedLargeResults(value byte) (largeValue, byte, largeValue) { + return makeLargeValue(value), value + 1, makeLargeValue(value + 2) +} + +func chooseLargeValue(flag bool) largeValue { + value := makeLargeValue(1) + if flag { + value = makeLargeValue(42) + } + return value +} + +func makePointerLargeValue(value *byte) largeStruct { + return largeStruct{ptr: value} +} + +func assertLargeValue(value any) byte { + large, ok := value.(largeValue) + if !ok { + return 0 + } + return large[len(large)-1] +} + +func useLargeMap(key, value largeValue) byte { + values := map[largeValue]largeValue{key: value} + result, ok := values[key] + if !ok { + return 0 + } + return result[len(result)-1] +} + +func useLargeChannel(ch chan largeValue, value largeValue) byte { + ch <- value + result, ok := <-ch + if !ok { + return 0 + } + return result[len(result)-1] +} + +func selectLargeChannel(ch chan largeValue, value largeValue) byte { + select { + case ch <- value: + return 0 + case result := <-ch: + return result[len(result)-1] + } +} diff --git a/compiler/testdata/large.ll b/compiler/testdata/large.ll new file mode 100644 index 0000000000..27da846060 --- /dev/null +++ b/compiler/testdata/large.ll @@ -0,0 +1,498 @@ +; ModuleID = 'large.go' +source_filename = "large.go" +target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20" +target triple = "wasm32-unknown-wasi" + +%runtime._string = type { ptr, i32 } +%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 +@"reflect/types.type:pointer:basic:string" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:basic:string" }, align 4 + +declare void @runtime.trackPointer(ptr readonly captures(none), ptr, ptr) #0 + +; Function Attrs: nounwind +define hidden void @main.init(ptr %context) unnamed_addr #1 { +entry: + ret void +} + +; Function Attrs: nounwind +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: + 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"(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) #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) #3 + +; Function Attrs: nounwind +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) #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 = 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 void @main.makeZeroLargeValue(ptr dereferenceable_or_null(1025) %return, ptr %context) unnamed_addr #1 { +entry: + 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: + %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(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) #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: nounwind +define hidden i8 @main.useLargeValue(ptr %context) unnamed_addr #1 { +entry: + %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: + %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 + %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) #9 + unreachable +} + +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: + %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 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, ptr, ptr, ptr) #5 + +; Function Attrs: nounwind +define hidden void @main.deferLargeValue(ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 { +entry: + %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 + 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 + +rundefers.after: ; preds = %rundefers.end + ret void + +rundefers.block: ; preds = %entry + br label %rundefers.loophead + +rundefers.loophead: ; preds = %rundefers.callback0, %rundefers.block + %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 %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 %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 %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 + unreachable + +rundefers.end: ; preds = %rundefers.loophead + br label %rundefers.after + +recover: ; No predecessors! + ret void +} + +; Function Attrs: nounwind +define hidden void @main.goLargeValue(ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 { +entry: + %stackalloc = alloca i8, align 1 + %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 #6 { +entry: + %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 void @main.makeLargeResults(ptr dereferenceable_or_null(1026) %return, i8 %value, ptr %context) unnamed_addr #1 { +entry: + %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 void @main.makeTwoLargeResults(ptr dereferenceable_or_null(2050) %return, i8 %value, ptr %context) unnamed_addr #1 { +entry: + %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 void @main.makeMixedLargeResults(ptr dereferenceable_or_null(2051) %return, i8 %value, ptr %context) unnamed_addr #1 { +entry: + %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 void @main.chooseLargeValue(ptr dereferenceable_or_null(1025) %return, i1 %flag, ptr %context) unnamed_addr #1 { +entry: + %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 + %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 + %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 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) #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 = 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 +} + +; Function Attrs: nounwind +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) #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 + %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 + 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 + %1 = getelementptr inbounds nuw i8, ptr %large, i32 1024 + %2 = load i8, ptr %1, align 1 + ret i8 %2 + +if.then: ; preds = %typeassert.next + ret i8 0 +} + +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(ptr readonly dereferenceable_or_null(1025) %key, ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 { +entry: + %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) #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 + %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 +} + +declare i32 @runtime.hash32(ptr, i32, i32, ptr) #0 + +declare i1 @runtime.memequal(ptr, ptr, i32, ptr) #0 + +declare ptr @runtime.hashmapMakeGeneric(i32, i32, i32, ptr, ptr, ptr, ptr, ptr) #0 + +declare void @runtime.hashmapBinarySet(ptr dereferenceable_or_null(48), ptr, ptr, ptr) #0 + +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, ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 { +entry: + %chan.op1 = alloca %runtime.channelOp, align 8 + %chan.op = alloca %runtime.channelOp, align 8 + %stackalloc = alloca i8, align 1 + call void @llvm.lifetime.start.p0(ptr nonnull %chan.op) + 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) + %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 + %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, 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 + %stackalloc = alloca i8, 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 %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) #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) #9 + %1 = extractvalue { i32, i1 } %select.result, 0 + %2 = icmp eq i32 %1, 0 + br i1 %2, label %select.body, label %select.next + +select.body: ; preds = %entry + ret i8 0 + +select.next: ; preds = %entry + %3 = icmp eq i32 %1, 1 + 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) #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) #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 +} + +declare { i32, i1 } @runtime.chanSelect(ptr, ptr, i32, i32, ptr, i32, i32, ptr) #0 + +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 = { 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 6e5af1559d..9c1adf7247 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 {