Skip to content

Commit eaa54bc

Browse files
aykevldeadprogram
authored andcommitted
compiler,runtime: use LLVM intrinsics for memcpy/memmove
This replaces the custom runtime.memcpy and runtime.memmove functions with calls to LLVM builtins that should hopefully allow LLVM to better optimize such calls. They will be lowered to regular libc memcpy/memmove when they can't be optimized away. When testing this change with some smoke tests, I found that many smoke tests resulted in slightly larger binary sizes with this commit applied. I looked into it and it appears that machine.sendUSBPacket was not inlined before while it is with this commit applied. Additionally, when I compared all driver smoke tests with -opt=1 I saw that many were reduced slightly in binary size and none increased in size.
1 parent c01f811 commit eaa54bc

File tree

4 files changed

+40
-29
lines changed

4 files changed

+40
-29
lines changed

compiler/compiler.go

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,6 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
330330
return c.ctx.CreateEnumAttribute(attrKind, 0)
331331
}
332332
nocapture := getAttr("nocapture")
333-
writeonly := getAttr("writeonly")
334333
readonly := getAttr("readonly")
335334

336335
// 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
354353
trackPointer.AddAttributeAtIndex(1, readonly)
355354
}
356355

357-
// Memory copy operations do not capture pointers, even though some weird
358-
// pointer arithmetic is happening in the Go implementation.
359-
for _, fnName := range []string{"runtime.memcpy", "runtime.memmove"} {
360-
fn := c.mod.NamedFunction(fnName)
361-
fn.AddAttributeAtIndex(1, nocapture)
362-
fn.AddAttributeAtIndex(1, writeonly)
363-
fn.AddAttributeAtIndex(2, nocapture)
364-
fn.AddAttributeAtIndex(2, readonly)
365-
}
366-
367356
// see: https://reviews.llvm.org/D18355
368357
if c.Debug() {
369358
c.mod.AddNamedMetadataOperand("llvm.module.flags",
@@ -1352,6 +1341,8 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
13521341
// applied) function call. If it is anonymous, it may be a closure.
13531342
name := fn.RelString(nil)
13541343
switch {
1344+
case name == "runtime.memcpy" || name == "runtime.memmove" || name == "reflect.memcpy":
1345+
return b.createMemoryCopyCall(fn, instr.Args)
13551346
case name == "device/arm.ReadRegister" || name == "device/riscv.ReadRegister":
13561347
return b.createReadRegister(name, instr.Args)
13571348
case name == "device/arm.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm":

compiler/intrinsics.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package compiler
2+
3+
// This file contains helper functions to create calls to LLVM intrinsics.
4+
5+
import (
6+
"strconv"
7+
8+
"golang.org/x/tools/go/ssa"
9+
"tinygo.org/x/go-llvm"
10+
)
11+
12+
// createMemoryCopyCall creates a call to a builtin LLVM memcpy or memmove
13+
// function, declaring this function if needed. These calls are treated
14+
// specially by optimization passes possibly resulting in better generated code,
15+
// and will otherwise be lowered to regular libc memcpy/memmove calls.
16+
func (b *builder) createMemoryCopyCall(fn *ssa.Function, args []ssa.Value) (llvm.Value, error) {
17+
fnName := "llvm." + fn.Name() + ".p0i8.p0i8.i" + strconv.Itoa(b.uintptrType.IntTypeWidth())
18+
llvmFn := b.mod.NamedFunction(fnName)
19+
if llvmFn.IsNil() {
20+
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.i8ptrType, b.i8ptrType, b.uintptrType, b.ctx.Int1Type()}, false)
21+
llvmFn = llvm.AddFunction(b.mod, fnName, fnType)
22+
}
23+
var params []llvm.Value
24+
for _, param := range args {
25+
params = append(params, b.getValue(param))
26+
}
27+
params = append(params, llvm.ConstInt(b.ctx.Int1Type(), 0, false))
28+
b.CreateCall(llvmFn, params, "")
29+
return llvm.Value{}, nil
30+
}

src/reflect/value.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -678,5 +678,6 @@ func (e *ValueError) Error() string {
678678
return "reflect: call of reflect.Value." + e.Method + " on invalid type"
679679
}
680680

681-
//go:linkname memcpy runtime.memcpy
681+
// Calls to this function are converted to LLVM intrinsic calls such as
682+
// llvm.memcpy.p0i8.p0i8.i32().
682683
func memcpy(dst, src unsafe.Pointer, size uintptr)

src/runtime/runtime.go

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -30,26 +30,15 @@ func os_runtime_args() []string {
3030
}
3131

3232
// Copy size bytes from src to dst. The memory areas must not overlap.
33-
func memcpy(dst, src unsafe.Pointer, size uintptr) {
34-
for i := uintptr(0); i < size; i++ {
35-
*(*uint8)(unsafe.Pointer(uintptr(dst) + i)) = *(*uint8)(unsafe.Pointer(uintptr(src) + i))
36-
}
37-
}
33+
// Calls to this function are converted to LLVM intrinsic calls such as
34+
// llvm.memcpy.p0i8.p0i8.i32(dst, src, size, false).
35+
func memcpy(dst, src unsafe.Pointer, size uintptr)
3836

3937
// Copy size bytes from src to dst. The memory areas may overlap and will do the
4038
// correct thing.
41-
func memmove(dst, src unsafe.Pointer, size uintptr) {
42-
if uintptr(dst) < uintptr(src) {
43-
// Copy forwards.
44-
memcpy(dst, src, size)
45-
return
46-
}
47-
// Copy backwards.
48-
for i := size; i != 0; {
49-
i--
50-
*(*uint8)(unsafe.Pointer(uintptr(dst) + i)) = *(*uint8)(unsafe.Pointer(uintptr(src) + i))
51-
}
52-
}
39+
// Calls to this function are converted to LLVM intrinsic calls such as
40+
// llvm.memmove.p0i8.p0i8.i32(dst, src, size, false).
41+
func memmove(dst, src unsafe.Pointer, size uintptr)
5342

5443
// Set the given number of bytes to zero.
5544
func memzero(ptr unsafe.Pointer, size uintptr) {

0 commit comments

Comments
 (0)