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