diff --git a/compiler/compiler.go b/compiler/compiler.go index 720eb8df..561095b0 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -330,7 +330,6 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con return c.ctx.CreateEnumAttribute(attrKind, 0) } nocapture := getAttr("nocapture") - writeonly := getAttr("writeonly") readonly := getAttr("readonly") // Tell the optimizer that runtime.alloc is an allocator, meaning that it @@ -354,16 +353,6 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con trackPointer.AddAttributeAtIndex(1, readonly) } - // Memory copy operations do not capture pointers, even though some weird - // pointer arithmetic is happening in the Go implementation. - for _, fnName := range []string{"runtime.memcpy", "runtime.memmove"} { - fn := c.mod.NamedFunction(fnName) - fn.AddAttributeAtIndex(1, nocapture) - fn.AddAttributeAtIndex(1, writeonly) - fn.AddAttributeAtIndex(2, nocapture) - fn.AddAttributeAtIndex(2, readonly) - } - // see: https://reviews.llvm.org/D18355 if c.Debug() { c.mod.AddNamedMetadataOperand("llvm.module.flags", @@ -1352,6 +1341,8 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) // applied) function call. If it is anonymous, it may be a closure. name := fn.RelString(nil) switch { + case name == "runtime.memcpy" || name == "runtime.memmove" || name == "reflect.memcpy": + return b.createMemoryCopyCall(fn, instr.Args) case name == "device/arm.ReadRegister" || name == "device/riscv.ReadRegister": return b.createReadRegister(name, instr.Args) case name == "device/arm.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm": diff --git a/compiler/intrinsics.go b/compiler/intrinsics.go new file mode 100644 index 00000000..4790b70d --- /dev/null +++ b/compiler/intrinsics.go @@ -0,0 +1,30 @@ +package compiler + +// This file contains helper functions to create calls to LLVM intrinsics. + +import ( + "strconv" + + "golang.org/x/tools/go/ssa" + "tinygo.org/x/go-llvm" +) + +// createMemoryCopyCall creates a call to a builtin LLVM memcpy or memmove +// function, declaring this function if needed. These calls are treated +// specially by optimization passes possibly resulting in better generated code, +// and will otherwise be lowered to regular libc memcpy/memmove calls. +func (b *builder) createMemoryCopyCall(fn *ssa.Function, args []ssa.Value) (llvm.Value, error) { + fnName := "llvm." + fn.Name() + ".p0i8.p0i8.i" + strconv.Itoa(b.uintptrType.IntTypeWidth()) + llvmFn := b.mod.NamedFunction(fnName) + if llvmFn.IsNil() { + fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.i8ptrType, b.i8ptrType, b.uintptrType, b.ctx.Int1Type()}, false) + llvmFn = llvm.AddFunction(b.mod, fnName, fnType) + } + var params []llvm.Value + for _, param := range args { + params = append(params, b.getValue(param)) + } + params = append(params, llvm.ConstInt(b.ctx.Int1Type(), 0, false)) + b.CreateCall(llvmFn, params, "") + return llvm.Value{}, nil +} diff --git a/src/reflect/value.go b/src/reflect/value.go index ad3b1fcb..a1c69370 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -678,5 +678,6 @@ func (e *ValueError) Error() string { return "reflect: call of reflect.Value." + e.Method + " on invalid type" } -//go:linkname memcpy runtime.memcpy +// Calls to this function are converted to LLVM intrinsic calls such as +// llvm.memcpy.p0i8.p0i8.i32(). func memcpy(dst, src unsafe.Pointer, size uintptr) diff --git a/src/runtime/runtime.go b/src/runtime/runtime.go index ae9ab665..d545e151 100644 --- a/src/runtime/runtime.go +++ b/src/runtime/runtime.go @@ -30,26 +30,15 @@ func os_runtime_args() []string { } // Copy size bytes from src to dst. The memory areas must not overlap. -func memcpy(dst, src unsafe.Pointer, size uintptr) { - for i := uintptr(0); i < size; i++ { - *(*uint8)(unsafe.Pointer(uintptr(dst) + i)) = *(*uint8)(unsafe.Pointer(uintptr(src) + i)) - } -} +// Calls to this function are converted to LLVM intrinsic calls such as +// llvm.memcpy.p0i8.p0i8.i32(dst, src, size, false). +func memcpy(dst, src unsafe.Pointer, size uintptr) // Copy size bytes from src to dst. The memory areas may overlap and will do the // correct thing. -func memmove(dst, src unsafe.Pointer, size uintptr) { - if uintptr(dst) < uintptr(src) { - // Copy forwards. - memcpy(dst, src, size) - return - } - // Copy backwards. - for i := size; i != 0; { - i-- - *(*uint8)(unsafe.Pointer(uintptr(dst) + i)) = *(*uint8)(unsafe.Pointer(uintptr(src) + i)) - } -} +// Calls to this function are converted to LLVM intrinsic calls such as +// llvm.memmove.p0i8.p0i8.i32(dst, src, size, false). +func memmove(dst, src unsafe.Pointer, size uintptr) // Set the given number of bytes to zero. func memzero(ptr unsafe.Pointer, size uintptr) {