tinygo/src/runtime/print.go
Ayke van Laethem 431e51b8a0 runtime: use dedicated printfloat32
It can be unexpected that printing a float32 involves 64-bit floating
point routines, see for example:
https://github.com/tinygo-org/tinygo/issues/1415

This commit adds a dedicated printfloat32 instead just for printing
float32 values. It comes with a possible code size increase, but only if
both float32 and float64 values are printed. Therefore, this should be
an improvement in almost all cases.

I also tried using printfloat32 for everything (and casting a float64 to
float32 to print) but the printed values are slightly different,
breaking the testdata/math.go test for example.
2020-10-02 11:26:22 +02:00

382 строки
6,1 КиБ
Go

package runtime
import (
"unsafe"
)
type stringer interface {
String() string
}
//go:nobounds
func printstring(s string) {
for i := 0; i < len(s); i++ {
putchar(s[i])
}
}
func printuint8(n uint8) {
if TargetBits >= 32 {
printuint32(uint32(n))
} else {
prevdigits := n / 10
if prevdigits != 0 {
printuint8(prevdigits)
}
putchar(byte((n % 10) + '0'))
}
}
func printint8(n int8) {
if TargetBits >= 32 {
printint32(int32(n))
} else {
if n < 0 {
putchar('-')
n = -n
}
printuint8(uint8(n))
}
}
func printuint16(n uint16) {
printuint32(uint32(n))
}
func printint16(n int16) {
printint32(int32(n))
}
func printuint32(n uint32) {
if TargetBits == 8 {
// AVR-specific workaround on LLVM 10. Should be removed when we switch
// to LLVM 11.
prevdigits := n / 10
if prevdigits != 0 {
printuint32(prevdigits)
}
putchar(byte((n % 10) + '0'))
return
}
printuint64(uint64(n))
}
func printint32(n int32) {
// Print integer in signed big-endian base-10 notation, for humans to
// read.
if n < 0 {
putchar('-')
n = -n
}
printuint32(uint32(n))
}
//go:nobounds
func printuint64(n uint64) {
digits := [20]byte{} // enough to hold (2^64)-1
// Fill in all 10 digits.
firstdigit := 19 // digit index that isn't zero (by default, the last to handle '0' correctly)
for i := 19; i >= 0; i-- {
digit := byte(n%10 + '0')
digits[i] = digit
if digit != '0' {
firstdigit = i
}
n /= 10
}
// Print digits without the leading zeroes.
for i := firstdigit; i < 20; i++ {
putchar(digits[i])
}
}
func printint64(n int64) {
if n < 0 {
putchar('-')
n = -n
}
printuint64(uint64(n))
}
// printfloat32() was copied from the relevant source in the original Go
// implementation and modified to work with float32 instead of float64. It is
// copyright by the Go authors, licensed under the same BSD 3-clause license.
// See https://golang.org/LICENSE for details.
//
// It is a near-duplicate of printfloat64. This is done so that printing a
// float32 value doesn't involve float64 routines, which can be unexpected and a
// problem sometimes. It comes with a possible code size reduction if both
// printfloat32 and printfloat64 are used, which seems uncommon.
//
// Source:
// https://github.com/golang/go/blob/master/src/runtime/print.go
func printfloat32(v float32) {
switch {
case v != v:
printstring("NaN")
return
case v+v == v && v > 0:
printstring("+Inf")
return
case v+v == v && v < 0:
printstring("-Inf")
return
}
const n = 7 // digits printed
var buf [n + 7]byte
buf[0] = '+'
e := 0 // exp
if v == 0 {
if 1/v < 0 {
buf[0] = '-'
}
} else {
if v < 0 {
v = -v
buf[0] = '-'
}
// normalize
for v >= 10 {
e++
v /= 10
}
for v < 1 {
e--
v *= 10
}
// round
h := float32(5.0)
for i := 0; i < n; i++ {
h /= 10
}
v += h
if v >= 10 {
e++
v /= 10
}
}
// format +d.dddd+edd
for i := 0; i < n; i++ {
s := int(v)
buf[i+2] = byte(s + '0')
v -= float32(s)
v *= 10
}
buf[1] = buf[2]
buf[2] = '.'
buf[n+2] = 'e'
buf[n+3] = '+'
if e < 0 {
e = -e
buf[n+3] = '-'
}
buf[n+4] = byte(e/100) + '0'
buf[n+5] = byte(e/10)%10 + '0'
buf[n+6] = byte(e%10) + '0'
for _, c := range buf {
putchar(c)
}
}
// printfloat64() was copied from the relevant source in the original Go
// implementation. It is copyright by the Go authors, licensed under the same
// BSD 3-clause license. See https://golang.org/LICENSE for details.
//
// Source:
// https://github.com/golang/go/blob/master/src/runtime/print.go
func printfloat64(v float64) {
switch {
case v != v:
printstring("NaN")
return
case v+v == v && v > 0:
printstring("+Inf")
return
case v+v == v && v < 0:
printstring("-Inf")
return
}
const n = 7 // digits printed
var buf [n + 7]byte
buf[0] = '+'
e := 0 // exp
if v == 0 {
if 1/v < 0 {
buf[0] = '-'
}
} else {
if v < 0 {
v = -v
buf[0] = '-'
}
// normalize
for v >= 10 {
e++
v /= 10
}
for v < 1 {
e--
v *= 10
}
// round
h := 5.0
for i := 0; i < n; i++ {
h /= 10
}
v += h
if v >= 10 {
e++
v /= 10
}
}
// format +d.dddd+edd
for i := 0; i < n; i++ {
s := int(v)
buf[i+2] = byte(s + '0')
v -= float64(s)
v *= 10
}
buf[1] = buf[2]
buf[2] = '.'
buf[n+2] = 'e'
buf[n+3] = '+'
if e < 0 {
e = -e
buf[n+3] = '-'
}
buf[n+4] = byte(e/100) + '0'
buf[n+5] = byte(e/10)%10 + '0'
buf[n+6] = byte(e%10) + '0'
for _, c := range buf {
putchar(c)
}
}
func printcomplex64(c complex64) {
putchar('(')
printfloat32(real(c))
printfloat32(imag(c))
printstring("i)")
}
func printcomplex128(c complex128) {
putchar('(')
printfloat64(real(c))
printfloat64(imag(c))
printstring("i)")
}
func printspace() {
putchar(' ')
}
func printnl() {
putchar('\r')
putchar('\n')
}
func printitf(msg interface{}) {
switch msg := msg.(type) {
case bool:
print(msg)
case int:
print(msg)
case int8:
print(msg)
case int16:
print(msg)
case int32:
print(msg)
case int64:
print(msg)
case uint:
print(msg)
case uint8:
print(msg)
case uint16:
print(msg)
case uint32:
print(msg)
case uint64:
print(msg)
case uintptr:
print(msg)
case float32:
print(msg)
case float64:
print(msg)
case complex64:
print(msg)
case complex128:
print(msg)
case string:
print(msg)
case error:
print(msg.Error())
case stringer:
print(msg.String())
default:
// cast to underlying type
itf := *(*_interface)(unsafe.Pointer(&msg))
putchar('(')
switch unsafe.Sizeof(itf.typecode) {
case 2:
printuint16(uint16(itf.typecode))
case 4:
printuint32(uint32(itf.typecode))
case 8:
printuint64(uint64(itf.typecode))
}
putchar(':')
print(itf.value)
putchar(')')
}
}
func printmap(m *hashmap) {
print("map[")
if m == nil {
print("nil")
} else {
print(uint(m.count))
}
putchar(']')
}
func printptr(ptr uintptr) {
if ptr == 0 {
print("nil")
return
}
putchar('0')
putchar('x')
for i := 0; i < int(unsafe.Sizeof(ptr))*2; i++ {
nibble := byte(ptr >> (unsafe.Sizeof(ptr)*8 - 4))
if nibble < 10 {
putchar(nibble + '0')
} else {
putchar(nibble - 10 + 'a')
}
ptr <<= 4
}
}
func printbool(b bool) {
if b {
printstring("true")
} else {
printstring("false")
}
}