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 | ||||||
|  |  | ||||||
		Загрузка…
	
	Создание таблицы
		
		Сослаться в новой задаче
	
	 Ayke van Laethem
						Ayke van Laethem