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.
Этот коммит содержится в:
родитель
c01f81144e
коммит
eaa54bc7e3
4 изменённых файлов: 40 добавлений и 29 удалений
|
@ -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":
|
||||
|
|
30
compiler/intrinsics.go
Обычный файл
30
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
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче