diff --git a/builder/sizes_test.go b/builder/sizes_test.go index 7779d4adce..0f52f3c6be 100644 --- a/builder/sizes_test.go +++ b/builder/sizes_test.go @@ -44,7 +44,7 @@ func TestBinarySize(t *testing.T) { // microcontrollers {"hifive1b", "examples/echo", 3668, 280, 0, 2244}, {"microbit", "examples/serial", 2694, 342, 8, 2248}, - {"wioterminal", "examples/pininterrupt", 6833, 1491, 120, 6888}, + {"wioterminal", "examples/pininterrupt", 6837, 1491, 120, 6888}, // TODO: also check wasm. Right now this is difficult, because // wasm binaries are run through wasm-opt and therefore the diff --git a/compiler/compiler.go b/compiler/compiler.go index 34300af0c2..f8ce780919 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1682,13 +1682,41 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c case "copy": dst := argValues[0] src := argValues[1] + // Fetch the lengths. dstLen := b.CreateExtractValue(dst, 1, "copy.dstLen") srcLen := b.CreateExtractValue(src, 1, "copy.srcLen") - dstBuf := b.CreateExtractValue(dst, 0, "copy.dstArray") - srcBuf := b.CreateExtractValue(src, 0, "copy.srcArray") + // Find the minimum of the lengths. + minFuncName := "llvm.umin.i" + strconv.Itoa(b.uintptrType.IntTypeWidth()) + minFunc := b.mod.NamedFunction(minFuncName) + if minFunc.IsNil() { + fnType := llvm.FunctionType(b.uintptrType, []llvm.Type{b.uintptrType, b.uintptrType}, false) + minFunc = llvm.AddFunction(b.mod, minFuncName, fnType) + } + minLen := b.CreateCall(minFunc.GlobalValueType(), minFunc, []llvm.Value{dstLen, srcLen}, "copy.n") + // Multiply the length by the element size. elemType := b.getLLVMType(argTypes[0].Underlying().(*types.Slice).Elem()) elemSize := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(elemType), false) - return b.createRuntimeCall("sliceCopy", []llvm.Value{dstBuf, srcBuf, dstLen, srcLen, elemSize}, "copy.n"), nil + // NOTE: This is also NSW when uintptr is int, but we can only choose one through the C API? + size := b.CreateNUWMul(minLen, elemSize, "copy.size") + // Fetch the pointers. + dstBuf := b.CreateExtractValue(dst, 0, "copy.dstPtr") + srcBuf := b.CreateExtractValue(src, 0, "copy.srcPtr") + // Create a memcpy. + call := b.createMemCopy("memmove", dstBuf, srcBuf, size) + align := b.targetData.ABITypeAlignment(elemType) + if align > 1 { + // Apply the type's alignment to the arguments. + // LLVM sometimes turns constant-length moves into loads and stores. + // It may use this alignment for the created loads and stores. + alignAttr := b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align)) + call.AddCallSiteAttribute(1, alignAttr) + call.AddCallSiteAttribute(2, alignAttr) + } + // Extend and return the copied length. + if b.targetData.TypeAllocSize(minLen.Type()) < b.targetData.TypeAllocSize(b.intType) { + minLen = b.CreateZExt(minLen, b.intType, "copy.n.zext") + } + return minLen, nil case "delete": m := argValues[0] key := argValues[1] diff --git a/compiler/intrinsics.go b/compiler/intrinsics.go index 571009c861..d9d8bba5b8 100644 --- a/compiler/intrinsics.go +++ b/compiler/intrinsics.go @@ -50,19 +50,24 @@ func (b *builder) defineIntrinsicFunction() { // and will otherwise be lowered to regular libc memcpy/memmove calls. func (b *builder) createMemoryCopyImpl() { b.createFunctionStart(true) - fnName := "llvm." + b.fn.Name() + ".p0.p0.i" + strconv.Itoa(b.uintptrType.IntTypeWidth()) + params := b.fn.Params[0:3] + b.createMemCopy( + b.fn.Name(), + b.getValue(params[0], getPos(b.fn)), + b.getValue(params[1], getPos(b.fn)), + b.getValue(params[2], getPos(b.fn)), + ) + b.CreateRetVoid() +} + +func (b *builder) createMemCopy(kind string, dst, src, len llvm.Value) llvm.Value { + fnName := "llvm." + kind + ".p0.p0.i" + strconv.Itoa(b.uintptrType.IntTypeWidth()) llvmFn := b.mod.NamedFunction(fnName) if llvmFn.IsNil() { fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.dataPtrType, b.dataPtrType, b.uintptrType, b.ctx.Int1Type()}, false) llvmFn = llvm.AddFunction(b.mod, fnName, fnType) } - var params []llvm.Value - for _, param := range b.fn.Params { - params = append(params, b.getValue(param, getPos(b.fn))) - } - params = append(params, llvm.ConstInt(b.ctx.Int1Type(), 0, false)) - b.CreateCall(llvmFn.GlobalValueType(), llvmFn, params, "") - b.CreateRetVoid() + return b.CreateCall(llvmFn.GlobalValueType(), llvmFn, []llvm.Value{dst, src, len, llvm.ConstInt(b.ctx.Int1Type(), 0, false)}, "") } // createMemoryZeroImpl creates calls to llvm.memset.* to zero a block of diff --git a/compiler/symbol.go b/compiler/symbol.go index 3227246446..e50eba599d 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -184,12 +184,6 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value) // be modified. llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0)) - case "runtime.sliceCopy": - // Copying a slice won't capture any of the parameters. - llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("writeonly"), 0)) - llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) - llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0)) - llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) case "runtime.stringFromBytes": llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0)) diff --git a/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll b/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll index 2afa934e49..96e5a84801 100644 --- a/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll +++ b/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll @@ -17,8 +17,8 @@ entry: ; Function Attrs: nounwind define hidden void @main.regularFunctionGoroutine(ptr %context) unnamed_addr #1 { entry: - %stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @"main.regularFunction$gowrapper" to i32), ptr undef) #9 - call void @"internal/task.start"(i32 ptrtoint (ptr @"main.regularFunction$gowrapper" to i32), ptr nonnull inttoptr (i32 5 to ptr), i32 %stacksize, ptr undef) #9 + %stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @"main.regularFunction$gowrapper" to i32), ptr undef) #11 + call void @"internal/task.start"(i32 ptrtoint (ptr @"main.regularFunction$gowrapper" to i32), ptr nonnull inttoptr (i32 5 to ptr), i32 %stacksize, ptr undef) #11 ret void } @@ -28,7 +28,7 @@ declare void @main.regularFunction(i32, ptr) #2 define linkonce_odr void @"main.regularFunction$gowrapper"(ptr %0) unnamed_addr #3 { entry: %unpack.int = ptrtoint ptr %0 to i32 - call void @main.regularFunction(i32 %unpack.int, ptr undef) #9 + call void @main.regularFunction(i32 %unpack.int, ptr undef) #11 ret void } @@ -39,8 +39,8 @@ declare void @"internal/task.start"(i32, ptr, i32, ptr) #2 ; Function Attrs: nounwind define hidden void @main.inlineFunctionGoroutine(ptr %context) unnamed_addr #1 { entry: - %stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @"main.inlineFunctionGoroutine$1$gowrapper" to i32), ptr undef) #9 - call void @"internal/task.start"(i32 ptrtoint (ptr @"main.inlineFunctionGoroutine$1$gowrapper" to i32), ptr nonnull inttoptr (i32 5 to ptr), i32 %stacksize, ptr undef) #9 + %stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @"main.inlineFunctionGoroutine$1$gowrapper" to i32), ptr undef) #11 + call void @"internal/task.start"(i32 ptrtoint (ptr @"main.inlineFunctionGoroutine$1$gowrapper" to i32), ptr nonnull inttoptr (i32 5 to ptr), i32 %stacksize, ptr undef) #11 ret void } @@ -61,18 +61,18 @@ entry: ; Function Attrs: nounwind define hidden void @main.closureFunctionGoroutine(ptr %context) unnamed_addr #1 { entry: - %n = call align 4 dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 + %n = call align 4 dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #11 store i32 3, ptr %n, align 4 - %0 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr null, ptr undef) #9 + %0 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr null, ptr undef) #11 store i32 5, ptr %0, align 4 %1 = getelementptr inbounds nuw i8, ptr %0, i32 4 store ptr %n, ptr %1, align 4 - %stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr undef) #9 - call void @"internal/task.start"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr nonnull %0, i32 %stacksize, ptr undef) #9 + %stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr undef) #11 + call void @"internal/task.start"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr nonnull %0, i32 %stacksize, ptr undef) #11 %2 = load i32, ptr %n, align 4 - call void @runtime.printlock(ptr undef) #9 - call void @runtime.printint32(i32 %2, ptr undef) #9 - call void @runtime.printunlock(ptr undef) #9 + call void @runtime.printlock(ptr undef) #11 + call void @runtime.printint32(i32 %2, ptr undef) #11 + call void @runtime.printunlock(ptr undef) #11 ret void } @@ -102,14 +102,14 @@ declare void @runtime.printunlock(ptr) #2 ; Function Attrs: nounwind define hidden void @main.funcGoroutine(ptr %fn.context, ptr %fn.funcptr, ptr %context) unnamed_addr #1 { entry: - %0 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #9 + %0 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #11 store i32 5, ptr %0, align 4 %1 = getelementptr inbounds nuw i8, ptr %0, i32 4 store ptr %fn.context, ptr %1, align 4 %2 = getelementptr inbounds nuw i8, ptr %0, i32 8 store ptr %fn.funcptr, ptr %2, align 4 - %stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @main.funcGoroutine.gowrapper to i32), ptr undef) #9 - call void @"internal/task.start"(i32 ptrtoint (ptr @main.funcGoroutine.gowrapper to i32), ptr nonnull %0, i32 %stacksize, ptr undef) #9 + %stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @main.funcGoroutine.gowrapper to i32), ptr undef) #11 + call void @"internal/task.start"(i32 ptrtoint (ptr @main.funcGoroutine.gowrapper to i32), ptr nonnull %0, i32 %stacksize, ptr undef) #11 ret void } @@ -121,7 +121,7 @@ entry: %3 = load ptr, ptr %2, align 4 %4 = getelementptr inbounds nuw i8, ptr %0, i32 8 %5 = load ptr, ptr %4, align 4 - call void %5(i32 %1, ptr %3) #9 + call void %5(i32 %1, ptr %3) #11 ret void } @@ -134,16 +134,21 @@ entry: ; Function Attrs: nounwind define hidden void @main.copyBuiltinGoroutine(ptr %dst.data, i32 %dst.len, i32 %dst.cap, ptr %src.data, i32 %src.len, i32 %src.cap, ptr %context) unnamed_addr #1 { entry: - %copy.n = call i32 @runtime.sliceCopy(ptr %dst.data, ptr %src.data, i32 %dst.len, i32 %src.len, i32 1, ptr undef) #9 + %copy.n = call i32 @llvm.umin.i32(i32 %dst.len, i32 %src.len) + call void @llvm.memmove.p0.p0.i32(ptr align 1 %dst.data, ptr align 1 %src.data, i32 %copy.n, i1 false) ret void } -declare i32 @runtime.sliceCopy(ptr nocapture writeonly, ptr nocapture readonly, i32, i32, i32, ptr) #2 +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare i32 @llvm.umin.i32(i32, i32) #7 + +; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite) +declare void @llvm.memmove.p0.p0.i32(ptr nocapture writeonly, ptr nocapture readonly, i32, i1 immarg) #8 ; Function Attrs: nounwind define hidden void @main.closeBuiltinGoroutine(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #1 { entry: - call void @runtime.chanClose(ptr %ch, ptr undef) #9 + call void @runtime.chanClose(ptr %ch, ptr undef) #11 ret void } @@ -152,7 +157,7 @@ declare void @runtime.chanClose(ptr dereferenceable_or_null(36), ptr) #2 ; Function Attrs: nounwind define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 { entry: - %0 = call align 4 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #9 + %0 = call align 4 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #11 store ptr %itf.value, ptr %0, align 4 %1 = getelementptr inbounds nuw i8, ptr %0, i32 4 store ptr @"main$string", ptr %1, align 4 @@ -160,15 +165,15 @@ entry: store i32 4, ptr %2, align 4 %3 = getelementptr inbounds nuw i8, ptr %0, i32 12 store ptr %itf.typecode, ptr %3, align 4 - %stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr undef) #9 - call void @"internal/task.start"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr nonnull %0, i32 %stacksize, ptr undef) #9 + %stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr undef) #11 + call void @"internal/task.start"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr nonnull %0, i32 %stacksize, ptr undef) #11 ret void } -declare void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr, ptr, i32, ptr, ptr) #7 +declare void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr, ptr, i32, ptr, ptr) #9 ; Function Attrs: nounwind -define linkonce_odr void @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper"(ptr %0) unnamed_addr #8 { +define linkonce_odr void @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper"(ptr %0) unnamed_addr #10 { entry: %1 = load ptr, ptr %0, align 4 %2 = getelementptr inbounds nuw i8, ptr %0, i32 4 @@ -177,7 +182,7 @@ entry: %5 = load i32, ptr %4, align 4 %6 = getelementptr inbounds nuw i8, ptr %0, i32 12 %7 = load ptr, ptr %6, align 4 - call void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr %1, ptr %3, i32 %5, ptr %7, ptr undef) #9 + call void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr %1, ptr %3, i32 %5, ptr %7, ptr undef) #11 ret void } @@ -188,6 +193,8 @@ attributes #3 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb attributes #4 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="main.inlineFunctionGoroutine$1" } attributes #5 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="main.closureFunctionGoroutine$1" } attributes #6 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper" } -attributes #7 = { "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" } -attributes #8 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="interface:{Print:func:{basic:string}{}}.Print$invoke" } -attributes #9 = { nounwind } +attributes #7 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } +attributes #8 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) } +attributes #9 = { "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" } +attributes #10 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="interface:{Print:func:{basic:string}{}}.Print$invoke" } +attributes #11 = { nounwind } diff --git a/compiler/testdata/goroutine-wasm-asyncify.ll b/compiler/testdata/goroutine-wasm-asyncify.ll index 213e2749f7..75185afbf5 100644 --- a/compiler/testdata/goroutine-wasm-asyncify.ll +++ b/compiler/testdata/goroutine-wasm-asyncify.ll @@ -19,7 +19,7 @@ entry: ; Function Attrs: nounwind define hidden void @main.regularFunctionGoroutine(ptr %context) unnamed_addr #2 { entry: - call void @"internal/task.start"(i32 ptrtoint (ptr @"main.regularFunction$gowrapper" to i32), ptr nonnull inttoptr (i32 5 to ptr), i32 65536, ptr undef) #9 + call void @"internal/task.start"(i32 ptrtoint (ptr @"main.regularFunction$gowrapper" to i32), ptr nonnull inttoptr (i32 5 to ptr), i32 65536, ptr undef) #11 ret void } @@ -31,8 +31,8 @@ declare void @runtime.deadlock(ptr) #1 define linkonce_odr void @"main.regularFunction$gowrapper"(ptr %0) unnamed_addr #3 { entry: %unpack.int = ptrtoint ptr %0 to i32 - call void @main.regularFunction(i32 %unpack.int, ptr undef) #9 - call void @runtime.deadlock(ptr undef) #9 + call void @main.regularFunction(i32 %unpack.int, ptr undef) #11 + call void @runtime.deadlock(ptr undef) #11 unreachable } @@ -41,7 +41,7 @@ declare void @"internal/task.start"(i32, ptr, i32, ptr) #1 ; Function Attrs: nounwind define hidden void @main.inlineFunctionGoroutine(ptr %context) unnamed_addr #2 { entry: - call void @"internal/task.start"(i32 ptrtoint (ptr @"main.inlineFunctionGoroutine$1$gowrapper" to i32), ptr nonnull inttoptr (i32 5 to ptr), i32 65536, ptr undef) #9 + call void @"internal/task.start"(i32 ptrtoint (ptr @"main.inlineFunctionGoroutine$1$gowrapper" to i32), ptr nonnull inttoptr (i32 5 to ptr), i32 65536, ptr undef) #11 ret void } @@ -56,7 +56,7 @@ define linkonce_odr void @"main.inlineFunctionGoroutine$1$gowrapper"(ptr %0) unn entry: %unpack.int = ptrtoint ptr %0 to i32 call void @"main.inlineFunctionGoroutine$1"(i32 %unpack.int, ptr undef) - call void @runtime.deadlock(ptr undef) #9 + call void @runtime.deadlock(ptr undef) #11 unreachable } @@ -64,21 +64,21 @@ entry: define hidden void @main.closureFunctionGoroutine(ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 - %n = call align 4 dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %n, ptr nonnull %stackalloc, ptr undef) #9 + %n = call align 4 dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #11 + call void @runtime.trackPointer(ptr nonnull %n, ptr nonnull %stackalloc, ptr undef) #11 store i32 3, ptr %n, align 4 - call void @runtime.trackPointer(ptr nonnull %n, ptr nonnull %stackalloc, ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull @"main.closureFunctionGoroutine$1", ptr nonnull %stackalloc, ptr undef) #9 - %0 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr null, ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #9 + call void @runtime.trackPointer(ptr nonnull %n, ptr nonnull %stackalloc, ptr undef) #11 + call void @runtime.trackPointer(ptr nonnull @"main.closureFunctionGoroutine$1", ptr nonnull %stackalloc, ptr undef) #11 + %0 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr null, ptr undef) #11 + call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #11 store i32 5, ptr %0, align 4 %1 = getelementptr inbounds nuw i8, ptr %0, i32 4 store ptr %n, ptr %1, align 4 - call void @"internal/task.start"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr nonnull %0, i32 65536, ptr undef) #9 + call void @"internal/task.start"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr nonnull %0, i32 65536, ptr undef) #11 %2 = load i32, ptr %n, align 4 - call void @runtime.printlock(ptr undef) #9 - call void @runtime.printint32(i32 %2, ptr undef) #9 - call void @runtime.printunlock(ptr undef) #9 + call void @runtime.printlock(ptr undef) #11 + call void @runtime.printint32(i32 %2, ptr undef) #11 + call void @runtime.printunlock(ptr undef) #11 ret void } @@ -96,7 +96,7 @@ entry: %2 = getelementptr inbounds nuw i8, ptr %0, i32 4 %3 = load ptr, ptr %2, align 4 call void @"main.closureFunctionGoroutine$1"(i32 %1, ptr %3) - call void @runtime.deadlock(ptr undef) #9 + call void @runtime.deadlock(ptr undef) #11 unreachable } @@ -110,14 +110,14 @@ declare void @runtime.printunlock(ptr) #1 define hidden void @main.funcGoroutine(ptr %fn.context, ptr %fn.funcptr, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 - %0 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #9 + %0 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #11 + call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #11 store i32 5, ptr %0, align 4 %1 = getelementptr inbounds nuw i8, ptr %0, i32 4 store ptr %fn.context, ptr %1, align 4 %2 = getelementptr inbounds nuw i8, ptr %0, i32 8 store ptr %fn.funcptr, ptr %2, align 4 - call void @"internal/task.start"(i32 ptrtoint (ptr @main.funcGoroutine.gowrapper to i32), ptr nonnull %0, i32 65536, ptr undef) #9 + call void @"internal/task.start"(i32 ptrtoint (ptr @main.funcGoroutine.gowrapper to i32), ptr nonnull %0, i32 65536, ptr undef) #11 ret void } @@ -129,8 +129,8 @@ entry: %3 = load ptr, ptr %2, align 4 %4 = getelementptr inbounds nuw i8, ptr %0, i32 8 %5 = load ptr, ptr %4, align 4 - call void %5(i32 %1, ptr %3) #9 - call void @runtime.deadlock(ptr undef) #9 + call void %5(i32 %1, ptr %3) #11 + call void @runtime.deadlock(ptr undef) #11 unreachable } @@ -143,16 +143,21 @@ entry: ; Function Attrs: nounwind define hidden void @main.copyBuiltinGoroutine(ptr %dst.data, i32 %dst.len, i32 %dst.cap, ptr %src.data, i32 %src.len, i32 %src.cap, ptr %context) unnamed_addr #2 { entry: - %copy.n = call i32 @runtime.sliceCopy(ptr %dst.data, ptr %src.data, i32 %dst.len, i32 %src.len, i32 1, ptr undef) #9 + %copy.n = call i32 @llvm.umin.i32(i32 %dst.len, i32 %src.len) + call void @llvm.memmove.p0.p0.i32(ptr align 1 %dst.data, ptr align 1 %src.data, i32 %copy.n, i1 false) ret void } -declare i32 @runtime.sliceCopy(ptr nocapture writeonly, ptr nocapture readonly, i32, i32, i32, ptr) #1 +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare i32 @llvm.umin.i32(i32, i32) #7 + +; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite) +declare void @llvm.memmove.p0.p0.i32(ptr nocapture writeonly, ptr nocapture readonly, i32, i1 immarg) #8 ; Function Attrs: nounwind define hidden void @main.closeBuiltinGoroutine(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #2 { entry: - call void @runtime.chanClose(ptr %ch, ptr undef) #9 + call void @runtime.chanClose(ptr %ch, ptr undef) #11 ret void } @@ -162,8 +167,8 @@ declare void @runtime.chanClose(ptr dereferenceable_or_null(36), ptr) #1 define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 - %0 = call align 4 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #9 + %0 = call align 4 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #11 + call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #11 store ptr %itf.value, ptr %0, align 4 %1 = getelementptr inbounds nuw i8, ptr %0, i32 4 store ptr @"main$string", ptr %1, align 4 @@ -171,14 +176,14 @@ entry: store i32 4, ptr %2, align 4 %3 = getelementptr inbounds nuw i8, ptr %0, i32 12 store ptr %itf.typecode, ptr %3, align 4 - call void @"internal/task.start"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr nonnull %0, i32 65536, ptr undef) #9 + call void @"internal/task.start"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr nonnull %0, i32 65536, ptr undef) #11 ret void } -declare void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr, ptr, i32, ptr, ptr) #7 +declare void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr, ptr, i32, ptr, ptr) #9 ; Function Attrs: nounwind -define linkonce_odr void @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper"(ptr %0) unnamed_addr #8 { +define linkonce_odr void @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper"(ptr %0) unnamed_addr #10 { entry: %1 = load ptr, ptr %0, align 4 %2 = getelementptr inbounds nuw i8, ptr %0, i32 4 @@ -187,8 +192,8 @@ entry: %5 = load i32, ptr %4, align 4 %6 = getelementptr inbounds nuw i8, ptr %0, i32 12 %7 = load ptr, ptr %6, align 4 - call void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr %1, ptr %3, i32 %5, ptr %7, ptr undef) #9 - call void @runtime.deadlock(ptr undef) #9 + call void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr %1, ptr %3, i32 %5, ptr %7, ptr undef) #11 + call void @runtime.deadlock(ptr undef) #11 unreachable } @@ -199,6 +204,8 @@ attributes #3 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+cal attributes #4 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper"="main.inlineFunctionGoroutine$1" } 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.closureFunctionGoroutine$1" } attributes #6 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper" } -attributes #7 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" } -attributes #8 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper"="interface:{Print:func:{basic:string}{}}.Print$invoke" } -attributes #9 = { nounwind } +attributes #7 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } +attributes #8 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) } +attributes #9 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" } +attributes #10 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper"="interface:{Print:func:{basic:string}{}}.Print$invoke" } +attributes #11 = { nounwind } diff --git a/compiler/testdata/slice.ll b/compiler/testdata/slice.ll index 1ebb80c611..9ce4cd51a8 100644 --- a/compiler/testdata/slice.ll +++ b/compiler/testdata/slice.ll @@ -38,7 +38,7 @@ lookup.next: ; preds = %entry ret i32 %1 lookup.throw: ; preds = %entry - call void @runtime.lookupPanic(ptr undef) #3 + call void @runtime.lookupPanic(ptr undef) #5 unreachable } @@ -48,21 +48,21 @@ declare void @runtime.lookupPanic(ptr) #1 define hidden { ptr, i32, i32 } @main.sliceAppendValues(ptr %ints.data, i32 %ints.len, i32 %ints.cap, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 - %varargs = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 - call void @runtime.trackPointer(ptr nonnull %varargs, ptr nonnull %stackalloc, ptr undef) #3 + %varargs = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #5 + call void @runtime.trackPointer(ptr nonnull %varargs, ptr nonnull %stackalloc, ptr undef) #5 store i32 1, ptr %varargs, align 4 %0 = getelementptr inbounds nuw i8, ptr %varargs, i32 4 store i32 2, ptr %0, align 4 %1 = getelementptr inbounds nuw i8, ptr %varargs, i32 8 store i32 3, ptr %1, align 4 - %append.new = call { ptr, i32, i32 } @runtime.sliceAppend(ptr %ints.data, ptr nonnull %varargs, i32 %ints.len, i32 %ints.cap, i32 3, i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %append.new = call { ptr, i32, i32 } @runtime.sliceAppend(ptr %ints.data, ptr nonnull %varargs, i32 %ints.len, i32 %ints.cap, i32 3, i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #5 %append.newPtr = extractvalue { ptr, i32, i32 } %append.new, 0 %append.newLen = extractvalue { ptr, i32, i32 } %append.new, 1 %append.newCap = extractvalue { ptr, i32, i32 } %append.new, 2 %2 = insertvalue { ptr, i32, i32 } undef, ptr %append.newPtr, 0 %3 = insertvalue { ptr, i32, i32 } %2, i32 %append.newLen, 1 %4 = insertvalue { ptr, i32, i32 } %3, i32 %append.newCap, 2 - call void @runtime.trackPointer(ptr %append.newPtr, ptr nonnull %stackalloc, ptr undef) #3 + call void @runtime.trackPointer(ptr %append.newPtr, ptr nonnull %stackalloc, ptr undef) #5 ret { ptr, i32, i32 } %4 } @@ -72,25 +72,31 @@ declare { ptr, i32, i32 } @runtime.sliceAppend(ptr, ptr nocapture readonly, i32, define hidden { ptr, i32, i32 } @main.sliceAppendSlice(ptr %ints.data, i32 %ints.len, i32 %ints.cap, ptr %added.data, i32 %added.len, i32 %added.cap, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 - %append.new = call { ptr, i32, i32 } @runtime.sliceAppend(ptr %ints.data, ptr %added.data, i32 %ints.len, i32 %ints.cap, i32 %added.len, i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %append.new = call { ptr, i32, i32 } @runtime.sliceAppend(ptr %ints.data, ptr %added.data, i32 %ints.len, i32 %ints.cap, i32 %added.len, i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #5 %append.newPtr = extractvalue { ptr, i32, i32 } %append.new, 0 %append.newLen = extractvalue { ptr, i32, i32 } %append.new, 1 %append.newCap = extractvalue { ptr, i32, i32 } %append.new, 2 %0 = insertvalue { ptr, i32, i32 } undef, ptr %append.newPtr, 0 %1 = insertvalue { ptr, i32, i32 } %0, i32 %append.newLen, 1 %2 = insertvalue { ptr, i32, i32 } %1, i32 %append.newCap, 2 - call void @runtime.trackPointer(ptr %append.newPtr, ptr nonnull %stackalloc, ptr undef) #3 + call void @runtime.trackPointer(ptr %append.newPtr, ptr nonnull %stackalloc, ptr undef) #5 ret { ptr, i32, i32 } %2 } ; Function Attrs: nounwind define hidden i32 @main.sliceCopy(ptr %dst.data, i32 %dst.len, i32 %dst.cap, ptr %src.data, i32 %src.len, i32 %src.cap, ptr %context) unnamed_addr #2 { entry: - %copy.n = call i32 @runtime.sliceCopy(ptr %dst.data, ptr %src.data, i32 %dst.len, i32 %src.len, i32 4, ptr undef) #3 + %copy.n = call i32 @llvm.umin.i32(i32 %dst.len, i32 %src.len) + %copy.size = shl nuw i32 %copy.n, 2 + call void @llvm.memmove.p0.p0.i32(ptr align 4 %dst.data, ptr align 4 %src.data, i32 %copy.size, i1 false) ret i32 %copy.n } -declare i32 @runtime.sliceCopy(ptr nocapture writeonly, ptr nocapture readonly, i32, i32, i32, ptr) #1 +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare i32 @llvm.umin.i32(i32, i32) #3 + +; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite) +declare void @llvm.memmove.p0.p0.i32(ptr nocapture writeonly, ptr nocapture readonly, i32, i1 immarg) #4 ; Function Attrs: nounwind define hidden { ptr, i32, i32 } @main.makeByteSlice(i32 %len, ptr %context) unnamed_addr #2 { @@ -100,15 +106,15 @@ entry: br i1 %slice.maxcap, label %slice.throw, label %slice.next slice.next: ; preds = %entry - %makeslice.buf = call align 1 ptr @runtime.alloc(i32 %len, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %makeslice.buf = call align 1 ptr @runtime.alloc(i32 %len, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #5 %0 = insertvalue { ptr, i32, i32 } undef, ptr %makeslice.buf, 0 %1 = insertvalue { ptr, i32, i32 } %0, i32 %len, 1 %2 = insertvalue { ptr, i32, i32 } %1, i32 %len, 2 - call void @runtime.trackPointer(ptr nonnull %makeslice.buf, ptr nonnull %stackalloc, ptr undef) #3 + call void @runtime.trackPointer(ptr nonnull %makeslice.buf, ptr nonnull %stackalloc, ptr undef) #5 ret { ptr, i32, i32 } %2 slice.throw: ; preds = %entry - call void @runtime.slicePanic(ptr undef) #3 + call void @runtime.slicePanic(ptr undef) #5 unreachable } @@ -123,15 +129,15 @@ entry: slice.next: ; preds = %entry %makeslice.cap = shl nuw i32 %len, 1 - %makeslice.buf = call align 2 ptr @runtime.alloc(i32 %makeslice.cap, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %makeslice.buf = call align 2 ptr @runtime.alloc(i32 %makeslice.cap, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #5 %0 = insertvalue { ptr, i32, i32 } undef, ptr %makeslice.buf, 0 %1 = insertvalue { ptr, i32, i32 } %0, i32 %len, 1 %2 = insertvalue { ptr, i32, i32 } %1, i32 %len, 2 - call void @runtime.trackPointer(ptr nonnull %makeslice.buf, ptr nonnull %stackalloc, ptr undef) #3 + call void @runtime.trackPointer(ptr nonnull %makeslice.buf, ptr nonnull %stackalloc, ptr undef) #5 ret { ptr, i32, i32 } %2 slice.throw: ; preds = %entry - call void @runtime.slicePanic(ptr undef) #3 + call void @runtime.slicePanic(ptr undef) #5 unreachable } @@ -144,15 +150,15 @@ entry: slice.next: ; preds = %entry %makeslice.cap = mul i32 %len, 3 - %makeslice.buf = call align 1 ptr @runtime.alloc(i32 %makeslice.cap, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %makeslice.buf = call align 1 ptr @runtime.alloc(i32 %makeslice.cap, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #5 %0 = insertvalue { ptr, i32, i32 } undef, ptr %makeslice.buf, 0 %1 = insertvalue { ptr, i32, i32 } %0, i32 %len, 1 %2 = insertvalue { ptr, i32, i32 } %1, i32 %len, 2 - call void @runtime.trackPointer(ptr nonnull %makeslice.buf, ptr nonnull %stackalloc, ptr undef) #3 + call void @runtime.trackPointer(ptr nonnull %makeslice.buf, ptr nonnull %stackalloc, ptr undef) #5 ret { ptr, i32, i32 } %2 slice.throw: ; preds = %entry - call void @runtime.slicePanic(ptr undef) #3 + call void @runtime.slicePanic(ptr undef) #5 unreachable } @@ -165,15 +171,15 @@ entry: slice.next: ; preds = %entry %makeslice.cap = shl nuw i32 %len, 2 - %makeslice.buf = call align 4 ptr @runtime.alloc(i32 %makeslice.cap, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %makeslice.buf = call align 4 ptr @runtime.alloc(i32 %makeslice.cap, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #5 %0 = insertvalue { ptr, i32, i32 } undef, ptr %makeslice.buf, 0 %1 = insertvalue { ptr, i32, i32 } %0, i32 %len, 1 %2 = insertvalue { ptr, i32, i32 } %1, i32 %len, 2 - call void @runtime.trackPointer(ptr nonnull %makeslice.buf, ptr nonnull %stackalloc, ptr undef) #3 + call void @runtime.trackPointer(ptr nonnull %makeslice.buf, ptr nonnull %stackalloc, ptr undef) #5 ret { ptr, i32, i32 } %2 slice.throw: ; preds = %entry - call void @runtime.slicePanic(ptr undef) #3 + call void @runtime.slicePanic(ptr undef) #5 unreachable } @@ -182,7 +188,7 @@ define hidden ptr @main.Add32(ptr %p, i32 %len, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 %0 = getelementptr i8, ptr %p, i32 %len - call void @runtime.trackPointer(ptr %0, ptr nonnull %stackalloc, ptr undef) #3 + call void @runtime.trackPointer(ptr %0, ptr nonnull %stackalloc, ptr undef) #5 ret ptr %0 } @@ -192,7 +198,7 @@ entry: %stackalloc = alloca i8, align 1 %0 = trunc i64 %len to i32 %1 = getelementptr i8, ptr %p, i32 %0 - call void @runtime.trackPointer(ptr %1, ptr nonnull %stackalloc, ptr undef) #3 + call void @runtime.trackPointer(ptr %1, ptr nonnull %stackalloc, ptr undef) #5 ret ptr %1 } @@ -206,7 +212,7 @@ slicetoarray.next: ; preds = %entry ret ptr %s.data slicetoarray.throw: ; preds = %entry - call void @runtime.sliceToArrayPointerPanic(ptr undef) #3 + call void @runtime.sliceToArrayPointerPanic(ptr undef) #5 unreachable } @@ -216,8 +222,8 @@ declare void @runtime.sliceToArrayPointerPanic(ptr) #1 define hidden ptr @main.SliceToArrayConst(ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 - %makeslice = call align 4 dereferenceable(24) ptr @runtime.alloc(i32 24, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 - call void @runtime.trackPointer(ptr nonnull %makeslice, ptr nonnull %stackalloc, ptr undef) #3 + %makeslice = call align 4 dereferenceable(24) ptr @runtime.alloc(i32 24, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #5 + call void @runtime.trackPointer(ptr nonnull %makeslice, ptr nonnull %stackalloc, ptr undef) #5 br i1 false, label %slicetoarray.throw, label %slicetoarray.next slicetoarray.next: ; preds = %entry @@ -242,11 +248,11 @@ unsafe.Slice.next: ; preds = %entry %5 = insertvalue { ptr, i32, i32 } undef, ptr %ptr, 0 %6 = insertvalue { ptr, i32, i32 } %5, i32 %len, 1 %7 = insertvalue { ptr, i32, i32 } %6, i32 %len, 2 - call void @runtime.trackPointer(ptr %ptr, ptr nonnull %stackalloc, ptr undef) #3 + call void @runtime.trackPointer(ptr %ptr, ptr nonnull %stackalloc, ptr undef) #5 ret { ptr, i32, i32 } %7 unsafe.Slice.throw: ; preds = %entry - call void @runtime.unsafeSlicePanic(ptr undef) #3 + call void @runtime.unsafeSlicePanic(ptr undef) #5 unreachable } @@ -266,11 +272,11 @@ unsafe.Slice.next: ; preds = %entry %4 = insertvalue { ptr, i32, i32 } undef, ptr %ptr, 0 %5 = insertvalue { ptr, i32, i32 } %4, i32 %3, 1 %6 = insertvalue { ptr, i32, i32 } %5, i32 %3, 2 - call void @runtime.trackPointer(ptr %ptr, ptr nonnull %stackalloc, ptr undef) #3 + call void @runtime.trackPointer(ptr %ptr, ptr nonnull %stackalloc, ptr undef) #5 ret { ptr, i32, i32 } %6 unsafe.Slice.throw: ; preds = %entry - call void @runtime.unsafeSlicePanic(ptr undef) #3 + call void @runtime.unsafeSlicePanic(ptr undef) #5 unreachable } @@ -290,11 +296,11 @@ unsafe.Slice.next: ; preds = %entry %6 = insertvalue { ptr, i32, i32 } undef, ptr %ptr, 0 %7 = insertvalue { ptr, i32, i32 } %6, i32 %5, 1 %8 = insertvalue { ptr, i32, i32 } %7, i32 %5, 2 - call void @runtime.trackPointer(ptr %ptr, ptr nonnull %stackalloc, ptr undef) #3 + call void @runtime.trackPointer(ptr %ptr, ptr nonnull %stackalloc, ptr undef) #5 ret { ptr, i32, i32 } %8 unsafe.Slice.throw: ; preds = %entry - call void @runtime.unsafeSlicePanic(ptr undef) #3 + call void @runtime.unsafeSlicePanic(ptr undef) #5 unreachable } @@ -314,15 +320,17 @@ unsafe.Slice.next: ; preds = %entry %6 = insertvalue { ptr, i32, i32 } undef, ptr %ptr, 0 %7 = insertvalue { ptr, i32, i32 } %6, i32 %5, 1 %8 = insertvalue { ptr, i32, i32 } %7, i32 %5, 2 - call void @runtime.trackPointer(ptr %ptr, ptr nonnull %stackalloc, ptr undef) #3 + call void @runtime.trackPointer(ptr %ptr, ptr nonnull %stackalloc, ptr undef) #5 ret { ptr, i32, i32 } %8 unsafe.Slice.throw: ; preds = %entry - call void @runtime.unsafeSlicePanic(ptr undef) #3 + call void @runtime.unsafeSlicePanic(ptr undef) #5 unreachable } attributes #0 = { 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 #1 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #2 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } -attributes #3 = { nounwind } +attributes #3 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } +attributes #4 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) } +attributes #5 = { nounwind } diff --git a/interp/interp_test.go b/interp/interp_test.go index cac5650879..f5bdb81882 100644 --- a/interp/interp_test.go +++ b/interp/interp_test.go @@ -2,7 +2,6 @@ package interp import ( "os" - "strconv" "strings" "testing" "time" @@ -11,25 +10,17 @@ import ( ) func TestInterp(t *testing.T) { - llvmVersion, err := strconv.Atoi(strings.Split(llvm.Version, ".")[0]) - if err != nil { - // Note: this should never happen and if it does, it will always happen - // for a particular build because llvm.Version is a constant. - panic(err) - } for _, name := range []string{ "basic", "phi", - "slice-copy", "consteval", + "intrinsics", + "copy", "interface", "revert", "alloc", } { name := name // make local to this closure - if name == "slice-copy" && llvmVersion < 14 { - continue - } t.Run(name, func(t *testing.T) { t.Parallel() runTest(t, "testdata/"+name) diff --git a/interp/interpreter.go b/interp/interpreter.go index 629c17d56d..137938d9f3 100644 --- a/interp/interpreter.go +++ b/interp/interpreter.go @@ -312,33 +312,28 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent fmt.Fprintln(os.Stderr, indent+"runtime.alloc:", size, "->", ptr) } locals[inst.localIndex] = ptr - case callFn.name == "runtime.sliceCopy": - // sliceCopy implements the built-in copy function for slices. - // It is implemented here so that it can be used even if the - // runtime implementation is not available. Doing it this way - // may also be faster. - // Code: - // func sliceCopy(dst, src unsafe.Pointer, dstLen, srcLen uintptr, elemSize uintptr) int { - // n := srcLen - // if n > dstLen { - // n = dstLen - // } - // memmove(dst, src, n*elemSize) - // return int(n) - // } - dstLen := operands[3].Uint(r) - srcLen := operands[4].Uint(r) - elemSize := operands[5].Uint(r) - n := srcLen - if n > dstLen { - n = dstLen - } - if r.debug { - fmt.Fprintln(os.Stderr, indent+"copy:", operands[1], operands[2], n) + case strings.HasPrefix(callFn.name, "llvm.umin."): + locals[inst.localIndex] = makeLiteralInt(min(operands[1].Uint(r), operands[2].Uint(r)), inst.llvmInst.Type().IntTypeWidth()) + case strings.HasPrefix(callFn.name, "llvm.smin."): + locals[inst.localIndex] = makeLiteralInt(uint64(min(operands[1].Int(r), operands[2].Int(r))), inst.llvmInst.Type().IntTypeWidth()) + case strings.HasPrefix(callFn.name, "llvm.umax."): + locals[inst.localIndex] = makeLiteralInt(max(operands[1].Uint(r), operands[2].Uint(r)), inst.llvmInst.Type().IntTypeWidth()) + case strings.HasPrefix(callFn.name, "llvm.smax."): + locals[inst.localIndex] = makeLiteralInt(uint64(max(operands[1].Int(r), operands[2].Int(r))), inst.llvmInst.Type().IntTypeWidth()) + case strings.HasPrefix(callFn.name, "llvm.memcpy.p0") || strings.HasPrefix(callFn.name, "llvm.memmove.p0"): + // Copy a block of memory from one pointer to another. + if operands[4].Uint(r) != 0 { + // This is a volatile copy/move. + err := r.runAtRuntime(fn, inst, locals, &mem, indent) + if err != nil { + return nil, mem, err + } + continue } - if n != 0 { + nBytes := operands[3].Uint(r) + if nBytes != 0 { // Only try to copy bytes when there are any bytes to copy. - // This is not just an optimization. If one of the slices + // This is not just an optimization. If one of the pointers // (or both) are nil, the asPointer method call will fail // even though copying a nil slice is allowed. dst, err := operands[1].asPointer(r) @@ -363,11 +358,10 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent } continue } - nBytes := uint32(n * elemSize) srcObj := mem.get(src.index()) dstObj := mem.getWritable(dst.index()) if srcObj.buffer == nil || dstObj.buffer == nil { - // If the buffer is nil, it means the slice is external. + // If the buffer is nil, it means the memory is external. // This can happen for example when copying data out of // a //go:embed slice, which is not available at interp // time. @@ -380,33 +374,10 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent } dstBuf := dstObj.buffer.asRawValue(r) srcBuf := srcObj.buffer.asRawValue(r) - copy(dstBuf.buf[dst.offset():dst.offset()+nBytes], srcBuf.buf[src.offset():]) + copy(dstBuf.buf[dst.offset():][:nBytes], srcBuf.buf[src.offset():][:nBytes]) dstObj.buffer = dstBuf mem.put(dst.index(), dstObj) } - locals[inst.localIndex] = makeLiteralInt(n, inst.llvmInst.Type().IntTypeWidth()) - case strings.HasPrefix(callFn.name, "llvm.memcpy.p0") || strings.HasPrefix(callFn.name, "llvm.memmove.p0"): - // Copy a block of memory from one pointer to another. - dst, err := operands[1].asPointer(r) - if err != nil { - return nil, mem, r.errorAt(inst, err) - } - src, err := operands[2].asPointer(r) - if err != nil { - return nil, mem, r.errorAt(inst, err) - } - nBytes := uint32(operands[3].Uint(r)) - dstObj := mem.getWritable(dst.index()) - dstBuf := dstObj.buffer.asRawValue(r) - if mem.get(src.index()).buffer == nil { - // Looks like the source buffer is not defined. - // This can happen with //extern or //go:embed. - return nil, mem, r.errorAt(inst, errUnsupportedRuntimeInst) - } - srcBuf := mem.get(src.index()).buffer.asRawValue(r) - copy(dstBuf.buf[dst.offset():dst.offset()+nBytes], srcBuf.buf[src.offset():]) - dstObj.buffer = dstBuf - mem.put(dst.index(), dstObj) case callFn.name == "runtime.typeAssert": // This function must be implemented manually as it is normally // implemented by the interface lowering pass. diff --git a/interp/testdata/copy.ll b/interp/testdata/copy.ll new file mode 100644 index 0000000000..f389be340a --- /dev/null +++ b/interp/testdata/copy.ll @@ -0,0 +1,68 @@ +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64--linux" + +@string = internal unnamed_addr constant [3 x i8] c"foo" +@moveDst = global [3 x i8] zeroinitializer +@copyDst = global [3 x i8] zeroinitializer + +@externalSrc = external global [2 x i8] +@moveExternalDst = global [2 x i8] zeroinitializer + +@moveEscapedSrc = global [4 x i8] c"abcd" +@moveEscapedDst = global [4 x i8] zeroinitializer + +@volatileSrc = global [2 x i8] c"xy" +@volatileDst = global [2 x i8] zeroinitializer + +declare void @use(ptr) + +define void @runtime.initAll() { + call void @main.init() + ret void +} + +define internal void @main.init() { + call void @testMove() + call void @testCopy() + call void @testMoveExternal() + call void @testMoveEscaped() + call void @testVolatileCopy() + ret void +} + +; Test a simple memmove between globals. +define internal void @testMove() { + call void @llvm.memmove.p0.p0.i64(ptr @moveDst, ptr @string, i64 3, i1 false) + ret void +} + +; Test a simple memcpy between globals. +define internal void @testCopy() { + call void @llvm.memcpy.p0.p0.i64(ptr @copyDst, ptr @string, i64 3, i1 false) + ret void +} + +; Test a memmove from an external global. +; This should be run at runtime. +define internal void @testMoveExternal() { + call void @llvm.memmove.p0.p0.i64(ptr @moveExternalDst, ptr @externalSrc, i64 2, i1 false) + ret void +} + +; Test a memmove from an escaped (and potentially modified) source buffer. +define internal void @testMoveEscaped() { + call void @use(ptr @moveEscapedSrc) + call void @llvm.memmove.p0.p0.i64(ptr @moveEscapedDst, ptr @moveEscapedSrc, i64 4, i1 false) + ret void +} + +; Test a volatile memcpy. +; This should always be run at runtime. +define internal void @testVolatileCopy() { + call void @llvm.memcpy.p0.p0.i64(ptr @volatileDst, ptr @volatileSrc, i64 2, i1 true) + ret void +} + +declare void @llvm.memmove.p0.p0.i64(ptr, ptr, i64, i1) + +declare void @llvm.memcpy.p0.p0.i64(ptr, ptr, i64, i1) diff --git a/interp/testdata/copy.out.ll b/interp/testdata/copy.out.ll new file mode 100644 index 0000000000..dcabd7b72d --- /dev/null +++ b/interp/testdata/copy.out.ll @@ -0,0 +1,27 @@ +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64--linux" + +@moveDst = local_unnamed_addr global [3 x i8] c"foo" +@copyDst = local_unnamed_addr global [3 x i8] c"foo" +@externalSrc = external local_unnamed_addr global [2 x i8] +@moveExternalDst = local_unnamed_addr global [2 x i8] zeroinitializer +@moveEscapedSrc = global [4 x i8] c"abcd" +@moveEscapedDst = local_unnamed_addr global [4 x i8] zeroinitializer +@volatileSrc = global [2 x i8] c"xy" +@volatileDst = global [2 x i8] zeroinitializer + +declare void @use(ptr) local_unnamed_addr + +define void @runtime.initAll() local_unnamed_addr { + call void @llvm.memmove.p0.p0.i64(ptr @moveExternalDst, ptr @externalSrc, i64 2, i1 false) + call void @use(ptr @moveEscapedSrc) + call void @llvm.memmove.p0.p0.i64(ptr @moveEscapedDst, ptr @moveEscapedSrc, i64 4, i1 false) + call void @llvm.memcpy.p0.p0.i64(ptr @volatileDst, ptr @volatileSrc, i64 2, i1 true) + ret void +} + +declare void @llvm.memmove.p0.p0.i64(ptr nocapture writeonly, ptr nocapture readonly, i64, i1 immarg) #0 + +declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #0 + +attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) } diff --git a/interp/testdata/intrinsics.ll b/interp/testdata/intrinsics.ll new file mode 100644 index 0000000000..f830d39d68 --- /dev/null +++ b/interp/testdata/intrinsics.ll @@ -0,0 +1,52 @@ +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64--linux" + +@uminResult = global i32 0 +@sminResult = global i32 0 +@umaxResult = global i32 0 +@smaxResult = global i32 0 + +define void @runtime.initAll() { + call void @main.init() + ret void +} + +define internal void @main.init() { + call void @testUMin() + call void @testSMin() + call void @testUMax() + call void @testSMax() + ret void +} + +define internal void @testUMin() { + %umin = call i32 @llvm.umin.i32(i32 12, i32 -1) + store i32 %umin, ptr @uminResult + ret void +} + +declare i32 @llvm.umin.i32(i32, i32) + +define internal void @testSMin() { + %smin = call i32 @llvm.smin.i32(i32 12, i32 -1) + store i32 %smin, ptr @sminResult + ret void +} + +declare i32 @llvm.smin.i32(i32, i32) + +define internal void @testUMax() { + %umax = call i32 @llvm.umax.i32(i32 12, i32 -1) + store i32 %umax, ptr @umaxResult + ret void +} + +declare i32 @llvm.umax.i32(i32, i32) + +define internal void @testSMax() { + %smax = call i32 @llvm.smax.i32(i32 12, i32 -1) + store i32 %smax, ptr @smaxResult + ret void +} + +declare i32 @llvm.smax.i32(i32, i32) diff --git a/interp/testdata/intrinsics.out.ll b/interp/testdata/intrinsics.out.ll new file mode 100644 index 0000000000..e7bd2a6e79 --- /dev/null +++ b/interp/testdata/intrinsics.out.ll @@ -0,0 +1,11 @@ +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64--linux" + +@uminResult = local_unnamed_addr global i32 12 +@sminResult = local_unnamed_addr global i32 -1 +@umaxResult = local_unnamed_addr global i32 -1 +@smaxResult = local_unnamed_addr global i32 12 + +define void @runtime.initAll() local_unnamed_addr { + ret void +} diff --git a/interp/testdata/slice-copy.ll b/interp/testdata/slice-copy.ll deleted file mode 100644 index e3e3dedfef..0000000000 --- a/interp/testdata/slice-copy.ll +++ /dev/null @@ -1,124 +0,0 @@ -target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" -target triple = "x86_64--linux" - -@main.uint8SliceSrc.buf = internal global [2 x i8] c"\03d" -@main.uint8SliceSrc = internal unnamed_addr global { ptr, i64, i64 } { ptr @main.uint8SliceSrc.buf, i64 2, i64 2 } -@main.uint8SliceDst = internal unnamed_addr global { ptr, i64, i64 } zeroinitializer -@main.int16SliceSrc.buf = internal global [3 x i16] [i16 5, i16 123, i16 1024] -@main.int16SliceSrc = internal unnamed_addr global { ptr, i64, i64 } { ptr @main.int16SliceSrc.buf, i64 3, i64 3 } -@main.int16SliceDst = internal unnamed_addr global { ptr, i64, i64 } zeroinitializer -@main.sliceSrcUntaint.buf = internal global [2 x i8] c"ab" -@main.sliceDstUntaint.buf = internal global [2 x i8] zeroinitializer -@main.sliceSrcTaint.buf = internal global [2 x i8] c"cd" -@main.sliceDstTaint.buf = internal global [2 x i8] zeroinitializer -@main.sliceSrcExternal1.buf = external global [2 x i8] -@main.sliceDstExternal1.buf = internal global [2 x i8] zeroinitializer -@main.sliceSrcExternal2.buf = internal global [2 x i8] zeroinitializer -@main.sliceDstExternal2.buf = external global [2 x i8] - -declare i64 @runtime.sliceCopy(ptr %dst, ptr %src, i64 %dstLen, i64 %srcLen, i64 %elemSize) unnamed_addr - -declare ptr @runtime.alloc(i64, ptr) unnamed_addr - -declare void @runtime.printuint8(i8) - -declare void @runtime.printint16(i16) - -declare void @use(ptr) - -define void @runtime.initAll() unnamed_addr { -entry: - call void @main.init() - ret void -} - -define void @main() unnamed_addr { -entry: - ; print(uintSliceSrc[0]) - %uint8SliceSrc.buf = load ptr, ptr @main.uint8SliceSrc - %uint8SliceSrc.val = load i8, ptr %uint8SliceSrc.buf - call void @runtime.printuint8(i8 %uint8SliceSrc.val) - - ; print(uintSliceDst[0]) - %uint8SliceDst.buf = load ptr, ptr @main.uint8SliceDst - %uint8SliceDst.val = load i8, ptr %uint8SliceDst.buf - call void @runtime.printuint8(i8 %uint8SliceDst.val) - - ; print(int16SliceSrc[0]) - %int16SliceSrc.buf = load ptr, ptr @main.int16SliceSrc - %int16SliceSrc.val = load i16, ptr %int16SliceSrc.buf - call void @runtime.printint16(i16 %int16SliceSrc.val) - - ; print(int16SliceDst[0]) - %int16SliceDst.buf = load ptr, ptr @main.int16SliceDst - %int16SliceDst.val = load i16, ptr %int16SliceDst.buf - call void @runtime.printint16(i16 %int16SliceDst.val) - - ; print(sliceDstUntaint[0]) - %sliceDstUntaint.val = load i8, ptr getelementptr inbounds (i8, ptr @main.sliceDstUntaint.buf, i32 0) - call void @runtime.printuint8(i8 %sliceDstUntaint.val) - - ; print(sliceDstTaint[0]) - %sliceDstTaint.val = load i8, ptr getelementptr inbounds (i8, ptr @main.sliceDstTaint.buf, i32 0) - call void @runtime.printuint8(i8 %sliceDstTaint.val) - - ; print(sliceDstExternal1[0]) - %sliceDstExternal1.val = load i8, ptr getelementptr inbounds (i8, ptr @main.sliceDstExternal1.buf, i32 0) - call void @runtime.printuint8(i8 %sliceDstExternal1.val) - - ; print(sliceDstExternal2[0]) - %sliceDstExternal2.val = load i8, ptr getelementptr inbounds (i8, ptr @main.sliceDstExternal2.buf, i32 0) - call void @runtime.printuint8(i8 %sliceDstExternal2.val) - - ret void -} - -define internal void @main.init() unnamed_addr { -entry: - ; equivalent of: - ; uint8SliceDst = make([]uint8, len(uint8SliceSrc)) - %uint8SliceSrc = load { ptr, i64, i64 }, ptr @main.uint8SliceSrc - %uint8SliceSrc.len = extractvalue { ptr, i64, i64 } %uint8SliceSrc, 1 - %uint8SliceDst.buf = call ptr @runtime.alloc(i64 %uint8SliceSrc.len, ptr null) - %0 = insertvalue { ptr, i64, i64 } undef, ptr %uint8SliceDst.buf, 0 - %1 = insertvalue { ptr, i64, i64 } %0, i64 %uint8SliceSrc.len, 1 - %2 = insertvalue { ptr, i64, i64 } %1, i64 %uint8SliceSrc.len, 2 - store { ptr, i64, i64 } %2, ptr @main.uint8SliceDst - - ; equivalent of: - ; copy(uint8SliceDst, uint8SliceSrc) - %uint8SliceSrc.buf = extractvalue { ptr, i64, i64 } %uint8SliceSrc, 0 - %copy.n = call i64 @runtime.sliceCopy(ptr %uint8SliceDst.buf, ptr %uint8SliceSrc.buf, i64 %uint8SliceSrc.len, i64 %uint8SliceSrc.len, i64 1) - - ; equivalent of: - ; int16SliceDst = make([]int16, len(int16SliceSrc)) - %int16SliceSrc = load { ptr, i64, i64 }, ptr @main.int16SliceSrc - %int16SliceSrc.len = extractvalue { ptr, i64, i64 } %int16SliceSrc, 1 - %int16SliceSrc.len.bytes = mul i64 %int16SliceSrc.len, 2 - %int16SliceDst.buf = call ptr @runtime.alloc(i64 %int16SliceSrc.len.bytes, ptr null) - %3 = insertvalue { ptr, i64, i64 } undef, ptr %int16SliceDst.buf, 0 - %4 = insertvalue { ptr, i64, i64 } %3, i64 %int16SliceSrc.len, 1 - %5 = insertvalue { ptr, i64, i64 } %4, i64 %int16SliceSrc.len, 2 - store { ptr, i64, i64 } %5, ptr @main.int16SliceDst - - ; equivalent of: - ; copy(int16SliceDst, int16SliceSrc) - %int16SliceSrc.buf = extractvalue { ptr, i64, i64 } %int16SliceSrc, 0 - %copy.n2 = call i64 @runtime.sliceCopy(ptr %int16SliceDst.buf, ptr %int16SliceSrc.buf, i64 %int16SliceSrc.len, i64 %int16SliceSrc.len, i64 2) - - ; Copy slice that has a known value. - %copy.n3 = call i64 @runtime.sliceCopy(ptr @main.sliceDstUntaint.buf, ptr @main.sliceSrcUntaint.buf, i64 2, i64 2, i64 1) - - ; Copy slice that might have been modified by the external @use call. - ; This is a fix for https://github.com/tinygo-org/tinygo/issues/3890. - call void @use(ptr @main.sliceSrcTaint.buf) - %copy.n4 = call i64 @runtime.sliceCopy(ptr @main.sliceDstTaint.buf, ptr @main.sliceSrcTaint.buf, i64 2, i64 2, i64 1) - - ; Test that copying from or into external buffers works correctly. - ; These copy operations must be done at runtime. - ; https://github.com/tinygo-org/tinygo/issues/4895 - %copy.n5 = call i64 @runtime.sliceCopy(ptr @main.sliceDstExternal1.buf, ptr @main.sliceSrcExternal1.buf, i64 2, i64 2, i64 1) - %copy.n6 = call i64 @runtime.sliceCopy(ptr @main.sliceDstExternal2.buf, ptr @main.sliceSrcExternal2.buf, i64 2, i64 2, i64 1) - - ret void -} diff --git a/interp/testdata/slice-copy.out.ll b/interp/testdata/slice-copy.out.ll deleted file mode 100644 index 4c167e7d8a..0000000000 --- a/interp/testdata/slice-copy.out.ll +++ /dev/null @@ -1,42 +0,0 @@ -target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" -target triple = "x86_64--linux" - -@main.sliceSrcTaint.buf = internal global [2 x i8] c"cd" -@main.sliceDstTaint.buf = internal global [2 x i8] zeroinitializer -@main.sliceSrcExternal1.buf = external global [2 x i8] -@main.sliceDstExternal1.buf = internal global [2 x i8] zeroinitializer -@main.sliceSrcExternal2.buf = internal global [2 x i8] zeroinitializer -@main.sliceDstExternal2.buf = external global [2 x i8] - -declare i64 @runtime.sliceCopy(ptr, ptr, i64, i64, i64) unnamed_addr - -declare void @runtime.printuint8(i8) local_unnamed_addr - -declare void @runtime.printint16(i16) local_unnamed_addr - -declare void @use(ptr) local_unnamed_addr - -define void @runtime.initAll() unnamed_addr { -entry: - call void @use(ptr @main.sliceSrcTaint.buf) - %copy.n4 = call i64 @runtime.sliceCopy(ptr @main.sliceDstTaint.buf, ptr @main.sliceSrcTaint.buf, i64 2, i64 2, i64 1) - %copy.n5 = call i64 @runtime.sliceCopy(ptr @main.sliceDstExternal1.buf, ptr @main.sliceSrcExternal1.buf, i64 2, i64 2, i64 1) - %copy.n6 = call i64 @runtime.sliceCopy(ptr @main.sliceDstExternal2.buf, ptr @main.sliceSrcExternal2.buf, i64 2, i64 2, i64 1) - ret void -} - -define void @main() unnamed_addr { -entry: - call void @runtime.printuint8(i8 3) - call void @runtime.printuint8(i8 3) - call void @runtime.printint16(i16 5) - call void @runtime.printint16(i16 5) - call void @runtime.printuint8(i8 97) - %sliceDstTaint.val = load i8, ptr @main.sliceDstTaint.buf, align 1 - call void @runtime.printuint8(i8 %sliceDstTaint.val) - %sliceDstExternal1.val = load i8, ptr @main.sliceDstExternal1.buf, align 1 - call void @runtime.printuint8(i8 %sliceDstExternal1.val) - %sliceDstExternal2.val = load i8, ptr @main.sliceDstExternal2.buf, align 1 - call void @runtime.printuint8(i8 %sliceDstExternal2.val) - ret void -} diff --git a/src/internal/reflectlite/value.go b/src/internal/reflectlite/value.go index 47f0462a7d..0236035c50 100644 --- a/src/internal/reflectlite/value.go +++ b/src/internal/reflectlite/value.go @@ -1738,6 +1738,9 @@ func (e *ValueError) Error() string { //go:linkname memcpy runtime.memcpy func memcpy(dst, src unsafe.Pointer, size uintptr) +//go:linkname memmove runtime.memmove +func memmove(dst, src unsafe.Pointer, size uintptr) + //go:linkname memzero runtime.memzero func memzero(ptr unsafe.Pointer, size uintptr) @@ -1747,9 +1750,6 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer //go:linkname sliceAppend runtime.sliceAppend func sliceAppend(srcBuf, elemsBuf unsafe.Pointer, srcLen, srcCap, elemsLen uintptr, elemSize uintptr, layout unsafe.Pointer) (unsafe.Pointer, uintptr, uintptr) -//go:linkname sliceCopy runtime.sliceCopy -func sliceCopy(dst, src unsafe.Pointer, dstLen, srcLen uintptr, elemSize uintptr) int - // Copy copies the contents of src into dst until either // dst has been filled or src has been exhausted. func Copy(dst, src Value) int { @@ -1779,7 +1779,10 @@ func Copy(dst, src Value) int { dst.checkRO() } - return sliceCopy(dstbuf, srcbuf, dstlen, srclen, dst.typecode.elem().Size()) + minLen := min(dstlen, srclen) + elemSize := dst.typecode.elem().Size() + memmove(dstbuf, srcbuf, minLen*elemSize) + return int(minLen) } func buflen(v Value) (unsafe.Pointer, uintptr) { diff --git a/src/runtime/slice.go b/src/runtime/slice.go index ea0821551a..5206634fd2 100644 --- a/src/runtime/slice.go +++ b/src/runtime/slice.go @@ -23,17 +23,6 @@ func sliceAppend(srcBuf, elemsBuf unsafe.Pointer, srcLen, srcCap, elemsLen, elem return srcBuf, newLen, srcCap } -// Builtin copy(dst, src) function: copy bytes from dst to src. -func sliceCopy(dst, src unsafe.Pointer, dstLen, srcLen uintptr, elemSize uintptr) int { - // n = min(srcLen, dstLen) - n := srcLen - if n > dstLen { - n = dstLen - } - memmove(dst, src, n*elemSize) - return int(n) -} - // sliceGrow returns a new slice with space for at least newCap elements func sliceGrow(oldBuf unsafe.Pointer, oldLen, oldCap, newCap, elemSize uintptr, layout unsafe.Pointer) (unsafe.Pointer, uintptr, uintptr) { if oldCap >= newCap {