Skip to content

LLVM memory builtins #995

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 4 additions & 11 deletions compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -357,16 +356,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",
Expand Down Expand Up @@ -1334,6 +1323,10 @@ 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 == "runtime.memzero":
return b.createMemoryZeroCall(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":
Expand Down
50 changes: 50 additions & 0 deletions compiler/intrinsics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
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
}

// createMemoryZeroCall creates calls to llvm.memset.* to zero a block of
// memory, declaring the function if needed. These calls will be lowered to
// regular libc memset calls if they aren't optimized out in a different way.
func (b *builder) createMemoryZeroCall(args []ssa.Value) (llvm.Value, error) {
fnName := "llvm.memset.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.ctx.Int8Type(), b.uintptrType, b.ctx.Int1Type()}, false)
llvmFn = llvm.AddFunction(b.mod, fnName, fnType)
}
params := []llvm.Value{
b.getValue(args[0]),
llvm.ConstInt(b.ctx.Int8Type(), 0, false),
b.getValue(args[1]),
llvm.ConstInt(b.ctx.Int1Type(), 0, false),
}
b.CreateCall(llvmFn, params, "")
return llvm.Value{}, nil
}
3 changes: 2 additions & 1 deletion src/reflect/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
31 changes: 9 additions & 22 deletions src/runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,33 +30,20 @@ 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) {
for i := uintptr(0); i < size; i++ {
*(*byte)(unsafe.Pointer(uintptr(ptr) + i)) = 0
}
}
// Calls to this function are converted to LLVM intrinsic calls such as
// llvm.memset.p0i8.i32(ptr, 0, size, false).
func memzero(ptr unsafe.Pointer, size uintptr)

// Compare two same-size buffers for equality.
func memequal(x, y unsafe.Pointer, n uintptr) bool {
Expand Down