compiler: move LLVM math builtin support into the compiler
This simplifies src/runtime/math.go, which I eventually want to remove entirely by moving the given functionality into the compiler.
Этот коммит содержится в:
родитель
ca7c849da3
коммит
58565b42cc
7 изменённых файлов: 138 добавлений и 37 удалений
|
@ -23,7 +23,7 @@ import (
|
||||||
// Version of the compiler pacakge. Must be incremented each time the compiler
|
// Version of the compiler pacakge. Must be incremented each time the compiler
|
||||||
// package changes in a way that affects the generated LLVM module.
|
// package changes in a way that affects the generated LLVM module.
|
||||||
// This version is independent of the TinyGo version number.
|
// This version is independent of the TinyGo version number.
|
||||||
const Version = 12 // last change: implement syscall.rawSyscallNoError
|
const Version = 13 // last change: implement LLVM math builtins in the compiler
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
llvm.InitializeAllTargets()
|
llvm.InitializeAllTargets()
|
||||||
|
@ -1298,6 +1298,11 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
|
||||||
return b.createMemoryCopyCall(fn, instr.Args)
|
return b.createMemoryCopyCall(fn, instr.Args)
|
||||||
case name == "runtime.memzero":
|
case name == "runtime.memzero":
|
||||||
return b.createMemoryZeroCall(instr.Args)
|
return b.createMemoryZeroCall(instr.Args)
|
||||||
|
case name == "math.Ceil" || name == "math.Floor" || name == "math.Sqrt" || name == "math.Trunc":
|
||||||
|
result, ok := b.createMathOp(instr)
|
||||||
|
if ok {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
case name == "device.Asm" || name == "device/arm.Asm" || name == "device/arm64.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm":
|
case name == "device.Asm" || name == "device/arm.Asm" || name == "device/arm64.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm":
|
||||||
return b.createInlineAsm(instr.Args)
|
return b.createInlineAsm(instr.Args)
|
||||||
case name == "device.AsmFull" || name == "device/arm.AsmFull" || name == "device/arm64.AsmFull" || name == "device/avr.AsmFull" || name == "device/riscv.AsmFull":
|
case name == "device.AsmFull" || name == "device/arm.AsmFull" || name == "device/arm64.AsmFull" || name == "device/avr.AsmFull" || name == "device/riscv.AsmFull":
|
||||||
|
|
|
@ -48,6 +48,8 @@ func TestCompiler(t *testing.T) {
|
||||||
{"pragma.go", ""},
|
{"pragma.go", ""},
|
||||||
{"goroutine.go", "wasm"},
|
{"goroutine.go", "wasm"},
|
||||||
{"goroutine.go", "cortex-m-qemu"},
|
{"goroutine.go", "cortex-m-qemu"},
|
||||||
|
{"intrinsics.go", "cortex-m-qemu"},
|
||||||
|
{"intrinsics.go", "wasm"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
|
|
|
@ -48,3 +48,57 @@ func (b *builder) createMemoryZeroCall(args []ssa.Value) (llvm.Value, error) {
|
||||||
b.CreateCall(llvmFn, params, "")
|
b.CreateCall(llvmFn, params, "")
|
||||||
return llvm.Value{}, nil
|
return llvm.Value{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mathToLLVMMapping = map[string]string{
|
||||||
|
"math.Sqrt": "llvm.sqrt.f64",
|
||||||
|
"math.Floor": "llvm.floor.f64",
|
||||||
|
"math.Ceil": "llvm.ceil.f64",
|
||||||
|
"math.Trunc": "llvm.trunc.f64",
|
||||||
|
}
|
||||||
|
|
||||||
|
// createMathOp tries to lower the given call as a LLVM math intrinsic, if
|
||||||
|
// possible. It returns the call result if possible, and a boolean whether it
|
||||||
|
// succeeded. If it doesn't succeed, the architecture doesn't support the given
|
||||||
|
// intrinsic.
|
||||||
|
func (b *builder) createMathOp(call *ssa.CallCommon) (llvm.Value, bool) {
|
||||||
|
// Check whether this intrinsic is supported on the given GOARCH.
|
||||||
|
// If it is unsupported, this can have two reasons:
|
||||||
|
//
|
||||||
|
// 1. LLVM can expand the intrinsic inline (using float instructions), but
|
||||||
|
// the result doesn't pass the tests of the math package.
|
||||||
|
// 2. LLVM cannot expand the intrinsic inline, will therefore lower it as a
|
||||||
|
// libm function call, but the libm function call also fails the math
|
||||||
|
// package tests.
|
||||||
|
//
|
||||||
|
// Whatever the implementation, it must pass the tests in the math package
|
||||||
|
// so unfortunately only the below intrinsic+architecture combinations are
|
||||||
|
// supported.
|
||||||
|
name := call.StaticCallee().RelString(nil)
|
||||||
|
switch name {
|
||||||
|
case "math.Ceil", "math.Floor", "math.Trunc":
|
||||||
|
if b.GOARCH != "wasm" && b.GOARCH != "arm64" {
|
||||||
|
return llvm.Value{}, false
|
||||||
|
}
|
||||||
|
case "math.Sqrt":
|
||||||
|
if b.GOARCH != "wasm" && b.GOARCH != "amd64" && b.GOARCH != "386" {
|
||||||
|
return llvm.Value{}, false
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return llvm.Value{}, false // only the above functions are supported.
|
||||||
|
}
|
||||||
|
|
||||||
|
llvmFn := b.mod.NamedFunction(mathToLLVMMapping[name])
|
||||||
|
if llvmFn.IsNil() {
|
||||||
|
// The intrinsic doesn't exist yet, so declare it.
|
||||||
|
// At the moment, all supported intrinsics have the form "double
|
||||||
|
// foo(double %x)" so we can hardcode the signature here.
|
||||||
|
llvmType := llvm.FunctionType(b.ctx.DoubleType(), []llvm.Type{b.ctx.DoubleType()}, false)
|
||||||
|
llvmFn = llvm.AddFunction(b.mod, mathToLLVMMapping[name], llvmType)
|
||||||
|
}
|
||||||
|
// Create a call to the intrinsic.
|
||||||
|
args := make([]llvm.Value, len(call.Args))
|
||||||
|
for i, arg := range call.Args {
|
||||||
|
args[i] = b.getValue(arg)
|
||||||
|
}
|
||||||
|
return b.CreateCall(llvmFn, args, ""), true
|
||||||
|
}
|
||||||
|
|
27
compiler/testdata/intrinsics-cortex-m-qemu.ll
предоставленный
Обычный файл
27
compiler/testdata/intrinsics-cortex-m-qemu.ll
предоставленный
Обычный файл
|
@ -0,0 +1,27 @@
|
||||||
|
; ModuleID = 'intrinsics.go'
|
||||||
|
source_filename = "intrinsics.go"
|
||||||
|
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
|
||||||
|
target triple = "armv7m-none-eabi"
|
||||||
|
|
||||||
|
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
|
||||||
|
|
||||||
|
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||||
|
entry:
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
define hidden double @main.mySqrt(double %x, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||||
|
entry:
|
||||||
|
%0 = call double @math.Sqrt(double %x, i8* undef, i8* undef)
|
||||||
|
ret double %0
|
||||||
|
}
|
||||||
|
|
||||||
|
declare double @math.Sqrt(double, i8*, i8*)
|
||||||
|
|
||||||
|
define hidden double @main.myTrunc(double %x, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||||
|
entry:
|
||||||
|
%0 = call double @math.Trunc(double %x, i8* undef, i8* undef)
|
||||||
|
ret double %0
|
||||||
|
}
|
||||||
|
|
||||||
|
declare double @math.Trunc(double, i8*, i8*)
|
31
compiler/testdata/intrinsics-wasm.ll
предоставленный
Обычный файл
31
compiler/testdata/intrinsics-wasm.ll
предоставленный
Обычный файл
|
@ -0,0 +1,31 @@
|
||||||
|
; ModuleID = 'intrinsics.go'
|
||||||
|
source_filename = "intrinsics.go"
|
||||||
|
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||||
|
target triple = "wasm32--wasi"
|
||||||
|
|
||||||
|
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
|
||||||
|
|
||||||
|
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||||
|
entry:
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
define hidden double @main.mySqrt(double %x, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||||
|
entry:
|
||||||
|
%0 = call double @llvm.sqrt.f64(double %x)
|
||||||
|
ret double %0
|
||||||
|
}
|
||||||
|
|
||||||
|
; Function Attrs: nounwind readnone speculatable willreturn
|
||||||
|
declare double @llvm.sqrt.f64(double) #0
|
||||||
|
|
||||||
|
define hidden double @main.myTrunc(double %x, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||||
|
entry:
|
||||||
|
%0 = call double @llvm.trunc.f64(double %x)
|
||||||
|
ret double %0
|
||||||
|
}
|
||||||
|
|
||||||
|
; Function Attrs: nounwind readnone speculatable willreturn
|
||||||
|
declare double @llvm.trunc.f64(double) #0
|
||||||
|
|
||||||
|
attributes #0 = { nounwind readnone speculatable willreturn }
|
14
compiler/testdata/intrinsics.go
предоставленный
Обычный файл
14
compiler/testdata/intrinsics.go
предоставленный
Обычный файл
|
@ -0,0 +1,14 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
// Test how intrinsics are lowered: either as regular calls to the math
|
||||||
|
// functions or as LLVM builtins (such as llvm.sqrt.f64).
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
|
func mySqrt(x float64) float64 {
|
||||||
|
return math.Sqrt(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func myTrunc(x float64) float64 {
|
||||||
|
return math.Trunc(x)
|
||||||
|
}
|
|
@ -56,15 +56,7 @@ func math_Cbrt(x float64) float64 { return math_cbrt(x) }
|
||||||
func math_cbrt(x float64) float64
|
func math_cbrt(x float64) float64
|
||||||
|
|
||||||
//go:linkname math_Ceil math.Ceil
|
//go:linkname math_Ceil math.Ceil
|
||||||
func math_Ceil(x float64) float64 {
|
func math_Ceil(x float64) float64 { return math_ceil(x) }
|
||||||
if GOARCH == "arm64" || GOARCH == "wasm" {
|
|
||||||
return llvm_ceil(x)
|
|
||||||
}
|
|
||||||
return math_ceil(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
//export llvm.ceil.f64
|
|
||||||
func llvm_ceil(x float64) float64
|
|
||||||
|
|
||||||
//go:linkname math_ceil math.ceil
|
//go:linkname math_ceil math.ceil
|
||||||
func math_ceil(x float64) float64
|
func math_ceil(x float64) float64
|
||||||
|
@ -112,15 +104,7 @@ func math_Exp2(x float64) float64 { return math_exp2(x) }
|
||||||
func math_exp2(x float64) float64
|
func math_exp2(x float64) float64
|
||||||
|
|
||||||
//go:linkname math_Floor math.Floor
|
//go:linkname math_Floor math.Floor
|
||||||
func math_Floor(x float64) float64 {
|
func math_Floor(x float64) float64 { return math_floor(x) }
|
||||||
if GOARCH == "arm64" || GOARCH == "wasm" {
|
|
||||||
return llvm_floor(x)
|
|
||||||
}
|
|
||||||
return math_floor(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
//export llvm.floor.f64
|
|
||||||
func llvm_floor(x float64) float64
|
|
||||||
|
|
||||||
//go:linkname math_floor math.floor
|
//go:linkname math_floor math.floor
|
||||||
func math_floor(x float64) float64
|
func math_floor(x float64) float64
|
||||||
|
@ -216,15 +200,7 @@ func math_Sinh(x float64) float64 { return math_sinh(x) }
|
||||||
func math_sinh(x float64) float64
|
func math_sinh(x float64) float64
|
||||||
|
|
||||||
//go:linkname math_Sqrt math.Sqrt
|
//go:linkname math_Sqrt math.Sqrt
|
||||||
func math_Sqrt(x float64) float64 {
|
func math_Sqrt(x float64) float64 { return math_sqrt(x) }
|
||||||
if GOARCH == "386" || GOARCH == "amd64" || GOARCH == "wasm" {
|
|
||||||
return llvm_sqrt(x)
|
|
||||||
}
|
|
||||||
return math_sqrt(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
//export llvm.sqrt.f64
|
|
||||||
func llvm_sqrt(x float64) float64
|
|
||||||
|
|
||||||
//go:linkname math_sqrt math.sqrt
|
//go:linkname math_sqrt math.sqrt
|
||||||
func math_sqrt(x float64) float64
|
func math_sqrt(x float64) float64
|
||||||
|
@ -242,15 +218,7 @@ func math_Tanh(x float64) float64 { return math_tanh(x) }
|
||||||
func math_tanh(x float64) float64
|
func math_tanh(x float64) float64
|
||||||
|
|
||||||
//go:linkname math_Trunc math.Trunc
|
//go:linkname math_Trunc math.Trunc
|
||||||
func math_Trunc(x float64) float64 {
|
func math_Trunc(x float64) float64 { return math_trunc(x) }
|
||||||
if GOARCH == "arm64" || GOARCH == "wasm" {
|
|
||||||
return llvm_trunc(x)
|
|
||||||
}
|
|
||||||
return math_trunc(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
//export llvm.trunc.f64
|
|
||||||
func llvm_trunc(x float64) float64
|
|
||||||
|
|
||||||
//go:linkname math_trunc math.trunc
|
//go:linkname math_trunc math.trunc
|
||||||
func math_trunc(x float64) float64
|
func math_trunc(x float64) float64
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче