mirror of
https://github.com/golang/go.git
synced 2026-06-27 03:11:23 +00:00
internal/strconv: work around escape analysis bug
For some reason, aggressive inlining with -l=4 during TestAbstractOriginSanity does not properly analyze ftoa[F] and concludes that dst escapes to the heap. An earlier CL disabled TestAbstractOriginSanity to fix the build. This CL re-enables TestAbstractOriginSanity and manually specializes ftoa[F] into ftoa32 and ftoa64, which avoids the bad escape analysis and lets us re-enable the test. For #79547. Change-Id: I5a87ef5c95761781b9fea2b22d2bb161e37897d5 Reviewed-on: https://go-review.googlesource.com/c/go/+/781160 TryBot-Bypass: Russ Cox <rsc@golang.org> Reviewed-by: David Chase <drchase@google.com> Reviewed-by: Michael Pratt <mpratt@google.com> Reviewed-by: Cherry Mui <cherryyz@google.com>
This commit is contained in:
parent
04ed01963e
commit
661e0c610e
2 changed files with 142 additions and 8 deletions
|
|
@ -828,9 +828,6 @@ func TestAbstractOriginSanity(t *testing.T) {
|
|||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
|
||||
// TODO(go.dev/issue/79547): -l=4 builds are temporarily broken.
|
||||
t.Skip("-l=4 builds are currently broken because they introduce an allocation in runtime.printfloat64")
|
||||
|
||||
mustHaveDWARF(t)
|
||||
abstractOriginSanity(t, "testdata/httptest", OptAllInl4)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,10 +58,10 @@ const (
|
|||
// for all formats other than 'b', it will be at least two digits.
|
||||
func FormatFloat(f float64, fmt byte, prec, bitSize int) string {
|
||||
if bitSize == 32 {
|
||||
return string(ftoa(make([]byte, 0, max(prec+4, 24)), float32(f), fmt, prec))
|
||||
return string(ftoa32(make([]byte, 0, max(prec+4, 24)), float32(f), fmt, prec))
|
||||
}
|
||||
if bitSize == 64 {
|
||||
return string(ftoa(make([]byte, 0, max(prec+4, 24)), f, fmt, prec))
|
||||
return string(ftoa64(make([]byte, 0, max(prec+4, 24)), f, fmt, prec))
|
||||
}
|
||||
panic("strconv: illegal FormatFloat bitSize")
|
||||
}
|
||||
|
|
@ -70,15 +70,152 @@ func FormatFloat(f float64, fmt byte, prec, bitSize int) string {
|
|||
// as generated by [FormatFloat], to dst and returns the extended buffer.
|
||||
func AppendFloat(dst []byte, f float64, fmt byte, prec, bitSize int) []byte {
|
||||
if bitSize == 32 {
|
||||
return ftoa(dst, float32(f), fmt, prec)
|
||||
return ftoa32(dst, float32(f), fmt, prec)
|
||||
}
|
||||
if bitSize == 64 {
|
||||
return ftoa(dst, f, fmt, prec)
|
||||
return ftoa64(dst, f, fmt, prec)
|
||||
}
|
||||
panic("strconv: illegal AppendFloat bitSize")
|
||||
}
|
||||
|
||||
func ftoa[F float32 | float64](dst []byte, val F, fmt byte, prec int) []byte {
|
||||
// TODO(rsc): This should be ftoa[F float32 | float64](dst []byte, val F, ...),
|
||||
// but due to some bad interaction between inlining, escape analysis, and generic functions,
|
||||
// the result appears to escape dst to the heap with -l=4, which breaks
|
||||
// TestAbstractOriginSanity. See go.dev/issue/79547.
|
||||
// For now we make two manual specializations ftoa32 and ftoa64 instead.
|
||||
|
||||
func ftoa32(dst []byte, val float32, fmt byte, prec int) []byte {
|
||||
type F = float32
|
||||
var b uint64
|
||||
var expBits, mantBits, bias int // parameterized constants
|
||||
switch 8 * unsafe.Sizeof(val) {
|
||||
case 32:
|
||||
b = uint64(float32bits(float32(val)))
|
||||
expBits = float32ExpBits
|
||||
mantBits = float32MantBits
|
||||
bias = float32Bias
|
||||
case 64:
|
||||
b = float64bits(float64(val))
|
||||
expBits = float64ExpBits
|
||||
mantBits = float64MantBits
|
||||
bias = float64Bias
|
||||
}
|
||||
|
||||
neg := b>>(expBits+mantBits) != 0
|
||||
exp := int(b>>mantBits) & (1<<expBits - 1)
|
||||
mant := b & (1<<mantBits - 1)
|
||||
if exp == 1<<expBits-1 {
|
||||
if mant != 0 {
|
||||
return append(dst, "NaN"...)
|
||||
}
|
||||
if neg {
|
||||
return append(dst, "-Inf"...)
|
||||
}
|
||||
return append(dst, "+Inf"...)
|
||||
}
|
||||
if exp == 0 {
|
||||
exp++
|
||||
} else {
|
||||
mant |= 1 << mantBits
|
||||
}
|
||||
exp += bias
|
||||
|
||||
// Pick off easy binary, hex formats.
|
||||
if fmt == 'b' {
|
||||
return fmtB(dst, neg, mant, exp-mantBits)
|
||||
}
|
||||
if fmt == 'x' || fmt == 'X' {
|
||||
return fmtX(dst, prec, fmt, neg, mant, exp, mantBits)
|
||||
}
|
||||
|
||||
// Pick off zero.
|
||||
if mant == 0 {
|
||||
return fmtEFG(dst, neg, nil, 0, 0, prec, fmt, prec < 0)
|
||||
}
|
||||
|
||||
// Negative precision means "only as much as needed to be exact."
|
||||
if prec < 0 {
|
||||
// Use fast unrounded scaling.
|
||||
var buf [32]byte
|
||||
s := 64 - bits.Len64(mant)
|
||||
m := mant << s
|
||||
e := exp - s
|
||||
d, p := shortFloat[F](m, e-mantBits)
|
||||
dp, nd := setDigits(buf[:], d, p, numDigits(d))
|
||||
// Precision for shortest representation mode.
|
||||
switch fmt {
|
||||
case 'e', 'E':
|
||||
prec = max(nd-1, 0)
|
||||
case 'f':
|
||||
prec = max(nd-dp, 0)
|
||||
case 'g', 'G':
|
||||
prec = nd
|
||||
}
|
||||
return fmtEFG(dst, neg, buf[:], dp, nd, prec, fmt, true)
|
||||
}
|
||||
|
||||
if optimize {
|
||||
// Fixed number of digits.
|
||||
digits := prec
|
||||
switch fmt {
|
||||
case 'f':
|
||||
// %f precision specifies digits after the decimal point.
|
||||
// Estimate an upper bound on the total number of digits needed.
|
||||
// ftoaFixed will shorten as needed according to prec.
|
||||
if exp >= 0 {
|
||||
digits = 1 + log10Pow2(1+exp) + prec
|
||||
} else {
|
||||
digits = 1 + prec - log10Pow2(-exp)
|
||||
}
|
||||
case 'e', 'E':
|
||||
digits++
|
||||
case 'g', 'G':
|
||||
if prec == 0 {
|
||||
prec = 1
|
||||
}
|
||||
digits = prec
|
||||
default:
|
||||
// Invalid mode.
|
||||
digits = 1
|
||||
}
|
||||
if digits <= 18 {
|
||||
// digits <= 0 happens for %f on very small numbers
|
||||
// and means that we're guaranteed to print all zeros.
|
||||
var buf [24]byte
|
||||
var dp, nd int
|
||||
if digits > 0 {
|
||||
s := 64 - bits.Len64(mant)
|
||||
m := mant << s
|
||||
e := exp - s
|
||||
d, p := fixedWidthFloat(m, e-mantBits, digits, prec, fmt)
|
||||
if d != 0 {
|
||||
dp, nd = setDigits(buf[:], d, p, numDigits(d))
|
||||
}
|
||||
}
|
||||
return fmtEFG(dst, neg, buf[:], dp, nd, prec, fmt, false)
|
||||
}
|
||||
}
|
||||
|
||||
// Slow bignum case. Only for non-shortest results.
|
||||
d := new(decimal)
|
||||
d.Assign(mant)
|
||||
d.Shift(exp - mantBits)
|
||||
switch fmt {
|
||||
case 'e', 'E':
|
||||
d.Round(prec + 1)
|
||||
case 'f':
|
||||
d.Round(d.dp + prec)
|
||||
case 'g', 'G':
|
||||
if prec == 0 {
|
||||
prec = 1
|
||||
}
|
||||
d.Round(prec)
|
||||
}
|
||||
return fmtEFG(dst, neg, d.d[:], d.dp, d.nd, prec, fmt, false)
|
||||
}
|
||||
|
||||
func ftoa64(dst []byte, val float64, fmt byte, prec int) []byte {
|
||||
type F = float64
|
||||
var b uint64
|
||||
var expBits, mantBits, bias int // parameterized constants
|
||||
switch 8 * unsafe.Sizeof(val) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue