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.
Этот коммит содержится в:
Ayke van Laethem 2021-08-06 15:49:02 +02:00 коммит произвёл Ron Evans
родитель ca7c849da3
коммит 58565b42cc
7 изменённых файлов: 138 добавлений и 37 удалений

Просмотреть файл

@ -23,7 +23,7 @@ import (
// Version of the compiler pacakge. Must be incremented each time the compiler
// package changes in a way that affects the generated LLVM module.
// 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() {
llvm.InitializeAllTargets()
@ -1298,6 +1298,11 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
return b.createMemoryCopyCall(fn, instr.Args)
case name == "runtime.memzero":
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":
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":

Просмотреть файл

@ -48,6 +48,8 @@ func TestCompiler(t *testing.T) {
{"pragma.go", ""},
{"goroutine.go", "wasm"},
{"goroutine.go", "cortex-m-qemu"},
{"intrinsics.go", "cortex-m-qemu"},
{"intrinsics.go", "wasm"},
}
for _, tc := range tests {

Просмотреть файл

@ -48,3 +48,57 @@ func (b *builder) createMemoryZeroCall(args []ssa.Value) (llvm.Value, error) {
b.CreateCall(llvmFn, params, "")
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 предоставленный Обычный файл
Просмотреть файл

@ -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 предоставленный Обычный файл
Просмотреть файл

@ -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 предоставленный Обычный файл
Просмотреть файл

@ -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
//go:linkname math_Ceil math.Ceil
func math_Ceil(x float64) float64 {
if GOARCH == "arm64" || GOARCH == "wasm" {
return llvm_ceil(x)
}
return math_ceil(x)
}
//export llvm.ceil.f64
func llvm_ceil(x float64) float64
func math_Ceil(x float64) float64 { return math_ceil(x) }
//go:linkname math_ceil math.ceil
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
//go:linkname math_Floor math.Floor
func math_Floor(x float64) float64 {
if GOARCH == "arm64" || GOARCH == "wasm" {
return llvm_floor(x)
}
return math_floor(x)
}
//export llvm.floor.f64
func llvm_floor(x float64) float64
func math_Floor(x float64) float64 { return math_floor(x) }
//go:linkname math_floor math.floor
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
//go:linkname math_Sqrt math.Sqrt
func math_Sqrt(x float64) float64 {
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
func math_Sqrt(x float64) float64 { return math_sqrt(x) }
//go:linkname math_sqrt math.sqrt
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
//go:linkname math_Trunc math.Trunc
func math_Trunc(x float64) float64 {
if GOARCH == "arm64" || GOARCH == "wasm" {
return llvm_trunc(x)
}
return math_trunc(x)
}
//export llvm.trunc.f64
func llvm_trunc(x float64) float64
func math_Trunc(x float64) float64 { return math_trunc(x) }
//go:linkname math_trunc math.trunc
func math_trunc(x float64) float64