aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Pratt <mpratt@google.com>2025-05-14 15:53:58 -0400
committerGopher Robot <gobot@golang.org>2025-05-19 12:46:19 -0700
commit76e7bfbb4e3a6114a33c7dba666fdd26698bedc5 (patch)
treea3c5738900dba5ff5e40f43ddda7dcb14b9ddae2
parentd93bea0e59e51c2d85e69f28f09726b5c9fc81d2 (diff)
downloadgo-76e7bfbb4e3a6114a33c7dba666fdd26698bedc5.tar.xz
runtime: move atoi to internal/runtime/strconv
Moving to a smaller package allows its use in other internal/runtime packages. This isn't internal/strconvlite since it can't be used directly by strconv. For #73193. Change-Id: I6a6a636c9c8b3f06b5fd6c07fe9dd5a7a37d1429 Reviewed-on: https://go-review.googlesource.com/c/go/+/672697 Reviewed-by: Michael Knyszek <mknyszek@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Michael Pratt <mpratt@google.com>
-rw-r--r--src/cmd/internal/objabi/pkgspecial.go1
-rw-r--r--src/go/build/deps_test.go1
-rw-r--r--src/internal/runtime/math/math.go3
-rw-r--r--src/internal/runtime/strconv/atoi.go76
-rw-r--r--src/internal/runtime/strconv/atoi_test.go105
-rw-r--r--src/runtime/export_test.go2
-rw-r--r--src/runtime/malloc.go4
-rw-r--r--src/runtime/mgcpacer.go6
-rw-r--r--src/runtime/mheap.go2
-rw-r--r--src/runtime/os_linux.go3
-rw-r--r--src/runtime/proc.go3
-rw-r--r--src/runtime/runtime1.go7
-rw-r--r--src/runtime/string.go83
-rw-r--r--src/runtime/string_test.go92
14 files changed, 208 insertions, 180 deletions
diff --git a/src/cmd/internal/objabi/pkgspecial.go b/src/cmd/internal/objabi/pkgspecial.go
index 55b66b6055..d4773b1ecf 100644
--- a/src/cmd/internal/objabi/pkgspecial.go
+++ b/src/cmd/internal/objabi/pkgspecial.go
@@ -53,6 +53,7 @@ var runtimePkgs = []string{
"internal/runtime/gc",
"internal/runtime/maps",
"internal/runtime/math",
+ "internal/runtime/strconv",
"internal/runtime/sys",
"internal/runtime/syscall",
diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go
index b261af47e2..4f366b34a1 100644
--- a/src/go/build/deps_test.go
+++ b/src/go/build/deps_test.go
@@ -97,6 +97,7 @@ var depsRules = `
< internal/runtime/gc
< internal/runtime/math
< internal/runtime/maps
+ < internal/runtime/strconv
< runtime
< sync/atomic
< internal/sync
diff --git a/src/internal/runtime/math/math.go b/src/internal/runtime/math/math.go
index e0fdc3438d..7b616cff79 100644
--- a/src/internal/runtime/math/math.go
+++ b/src/internal/runtime/math/math.go
@@ -8,7 +8,10 @@ import "internal/goarch"
const (
MaxUint32 = ^uint32(0)
+ MaxUint64 = ^uint64(0)
MaxUintptr = ^uintptr(0)
+
+ MaxInt64 = int64(MaxUint64 >> 1)
)
// MulUintptr returns a * b and whether the multiplication overflowed.
diff --git a/src/internal/runtime/strconv/atoi.go b/src/internal/runtime/strconv/atoi.go
new file mode 100644
index 0000000000..87b3faf6d5
--- /dev/null
+++ b/src/internal/runtime/strconv/atoi.go
@@ -0,0 +1,76 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package strconv
+
+import (
+ "internal/runtime/math"
+)
+
+// Atoi64 parses an int64 from a string s.
+// The bool result reports whether s is a number
+// representable by a value of type int64.
+func Atoi64(s string) (int64, bool) {
+ if s == "" {
+ return 0, false
+ }
+
+ neg := false
+ if s[0] == '-' {
+ neg = true
+ s = s[1:]
+ }
+
+ un := uint64(0)
+ for i := 0; i < len(s); i++ {
+ c := s[i]
+ if c < '0' || c > '9' {
+ return 0, false
+ }
+ if un > math.MaxUint64/10 {
+ // overflow
+ return 0, false
+ }
+ un *= 10
+ un1 := un + uint64(c) - '0'
+ if un1 < un {
+ // overflow
+ return 0, false
+ }
+ un = un1
+ }
+
+ if !neg && un > uint64(math.MaxInt64) {
+ return 0, false
+ }
+ if neg && un > uint64(math.MaxInt64)+1 {
+ return 0, false
+ }
+
+ n := int64(un)
+ if neg {
+ n = -n
+ }
+
+ return n, true
+}
+
+// Atoi is like Atoi64 but for integers
+// that fit into an int.
+func Atoi(s string) (int, bool) {
+ if n, ok := Atoi64(s); n == int64(int(n)) {
+ return int(n), ok
+ }
+ return 0, false
+}
+
+// Atoi32 is like Atoi but for integers
+// that fit into an int32.
+func Atoi32(s string) (int32, bool) {
+ if n, ok := Atoi64(s); n == int64(int32(n)) {
+ return int32(n), ok
+ }
+ return 0, false
+}
+
diff --git a/src/internal/runtime/strconv/atoi_test.go b/src/internal/runtime/strconv/atoi_test.go
new file mode 100644
index 0000000000..49cd6f160a
--- /dev/null
+++ b/src/internal/runtime/strconv/atoi_test.go
@@ -0,0 +1,105 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package strconv_test
+
+import (
+ "internal/runtime/strconv"
+ "testing"
+)
+
+const intSize = 32 << (^uint(0) >> 63)
+
+type atoi64Test struct {
+ in string
+ out int64
+ ok bool
+}
+
+var atoi64tests = []atoi64Test{
+ {"", 0, false},
+ {"0", 0, true},
+ {"-0", 0, true},
+ {"1", 1, true},
+ {"-1", -1, true},
+ {"12345", 12345, true},
+ {"-12345", -12345, true},
+ {"012345", 12345, true},
+ {"-012345", -12345, true},
+ {"12345x", 0, false},
+ {"-12345x", 0, false},
+ {"98765432100", 98765432100, true},
+ {"-98765432100", -98765432100, true},
+ {"20496382327982653440", 0, false},
+ {"-20496382327982653440", 0, false},
+ {"9223372036854775807", 1<<63 - 1, true},
+ {"-9223372036854775807", -(1<<63 - 1), true},
+ {"9223372036854775808", 0, false},
+ {"-9223372036854775808", -1 << 63, true},
+ {"9223372036854775809", 0, false},
+ {"-9223372036854775809", 0, false},
+}
+
+func TestAtoi(t *testing.T) {
+ switch intSize {
+ case 32:
+ for i := range atoi32tests {
+ test := &atoi32tests[i]
+ out, ok := strconv.Atoi(test.in)
+ if test.out != int32(out) || test.ok != ok {
+ t.Errorf("Atoi(%q) = (%v, %v) want (%v, %v)",
+ test.in, out, ok, test.out, test.ok)
+ }
+ }
+ case 64:
+ for i := range atoi64tests {
+ test := &atoi64tests[i]
+ out, ok := strconv.Atoi(test.in)
+ if test.out != int64(out) || test.ok != ok {
+ t.Errorf("Atoi(%q) = (%v, %v) want (%v, %v)",
+ test.in, out, ok, test.out, test.ok)
+ }
+ }
+ }
+}
+
+type atoi32Test struct {
+ in string
+ out int32
+ ok bool
+}
+
+var atoi32tests = []atoi32Test{
+ {"", 0, false},
+ {"0", 0, true},
+ {"-0", 0, true},
+ {"1", 1, true},
+ {"-1", -1, true},
+ {"12345", 12345, true},
+ {"-12345", -12345, true},
+ {"012345", 12345, true},
+ {"-012345", -12345, true},
+ {"12345x", 0, false},
+ {"-12345x", 0, false},
+ {"987654321", 987654321, true},
+ {"-987654321", -987654321, true},
+ {"2147483647", 1<<31 - 1, true},
+ {"-2147483647", -(1<<31 - 1), true},
+ {"2147483648", 0, false},
+ {"-2147483648", -1 << 31, true},
+ {"2147483649", 0, false},
+ {"-2147483649", 0, false},
+}
+
+func TestAtoi32(t *testing.T) {
+ for i := range atoi32tests {
+ test := &atoi32tests[i]
+ out, ok := strconv.Atoi32(test.in)
+ if test.out != out || test.ok != ok {
+ t.Errorf("Atoi32(%q) = (%v, %v) want (%v, %v)",
+ test.in, out, ok, test.out, test.ok)
+ }
+ }
+}
+
diff --git a/src/runtime/export_test.go b/src/runtime/export_test.go
index e7f5d426e4..a9cc767e30 100644
--- a/src/runtime/export_test.go
+++ b/src/runtime/export_test.go
@@ -35,8 +35,6 @@ var ReadRandomFailed = &readRandomFailed
var Fastlog2 = fastlog2
-var Atoi = atoi
-var Atoi32 = atoi32
var ParseByteCount = parseByteCount
var Nanotime = nanotime
diff --git a/src/runtime/malloc.go b/src/runtime/malloc.go
index ffaf92debc..ccdebb26fb 100644
--- a/src/runtime/malloc.go
+++ b/src/runtime/malloc.go
@@ -630,7 +630,7 @@ func mallocinit() {
}
// Initialize the memory limit here because the allocator is going to look at it
// but we haven't called gcinit yet and we're definitely going to allocate memory before then.
- gcController.memoryLimit.Store(maxInt64)
+ gcController.memoryLimit.Store(math.MaxInt64)
}
// sysAlloc allocates heap arena space for at least n bytes. The
@@ -1816,7 +1816,7 @@ func profilealloc(mp *m, x unsafe.Pointer, size uintptr) {
func nextSample() int64 {
if MemProfileRate == 0 {
// Basically never sample.
- return maxInt64
+ return math.MaxInt64
}
if MemProfileRate == 1 {
// Sample immediately.
diff --git a/src/runtime/mgcpacer.go b/src/runtime/mgcpacer.go
index 2e05244d95..044792d6bd 100644
--- a/src/runtime/mgcpacer.go
+++ b/src/runtime/mgcpacer.go
@@ -8,6 +8,8 @@ import (
"internal/cpu"
"internal/goexperiment"
"internal/runtime/atomic"
+ "internal/runtime/math"
+ "internal/runtime/strconv"
_ "unsafe" // for go:linkname
)
@@ -1311,7 +1313,7 @@ func readGOGC() int32 {
if p == "off" {
return -1
}
- if n, ok := atoi32(p); ok {
+ if n, ok := strconv.Atoi32(p); ok {
return n
}
return 100
@@ -1355,7 +1357,7 @@ func setMemoryLimit(in int64) (out int64) {
func readGOMEMLIMIT() int64 {
p := gogetenv("GOMEMLIMIT")
if p == "" || p == "off" {
- return maxInt64
+ return math.MaxInt64
}
n, ok := parseByteCount(p)
if !ok {
diff --git a/src/runtime/mheap.go b/src/runtime/mheap.go
index 5a27ab5e78..c5e55f583e 100644
--- a/src/runtime/mheap.go
+++ b/src/runtime/mheap.go
@@ -1324,7 +1324,7 @@ HaveSpan:
// that we expect to page in.
inuse := gcController.mappedReady.Load()
// Be careful about overflow, especially with uintptrs. Even on 32-bit platforms
- // someone can set a really big memory limit that isn't maxInt64.
+ // someone can set a really big memory limit that isn't math.MaxInt64.
if uint64(scav)+inuse > uint64(limit) {
bytesToScavenge = uintptr(uint64(scav) + inuse - uint64(limit))
forceScavenge = true
diff --git a/src/runtime/os_linux.go b/src/runtime/os_linux.go
index f24d18027b..3071e32202 100644
--- a/src/runtime/os_linux.go
+++ b/src/runtime/os_linux.go
@@ -8,6 +8,7 @@ import (
"internal/abi"
"internal/goarch"
"internal/runtime/atomic"
+ "internal/runtime/strconv"
"internal/runtime/syscall"
"unsafe"
)
@@ -341,7 +342,7 @@ func getHugePageSize() uintptr {
return 0
}
n-- // remove trailing newline
- v, ok := atoi(slicebytetostringtmp((*byte)(ptr), int(n)))
+ v, ok := strconv.Atoi(slicebytetostringtmp((*byte)(ptr), int(n)))
if !ok || v < 0 {
v = 0
}
diff --git a/src/runtime/proc.go b/src/runtime/proc.go
index 1ca800c5fd..5d3e4e4953 100644
--- a/src/runtime/proc.go
+++ b/src/runtime/proc.go
@@ -11,6 +11,7 @@ import (
"internal/goos"
"internal/runtime/atomic"
"internal/runtime/exithook"
+ "internal/runtime/strconv"
"internal/runtime/sys"
"internal/stringslite"
"unsafe"
@@ -900,7 +901,7 @@ func schedinit() {
lock(&sched.lock)
sched.lastpoll.Store(nanotime())
procs := ncpu
- if n, ok := atoi32(gogetenv("GOMAXPROCS")); ok && n > 0 {
+ if n, ok := strconv.Atoi32(gogetenv("GOMAXPROCS")); ok && n > 0 {
procs = n
}
if procresize(procs) != nil {
diff --git a/src/runtime/runtime1.go b/src/runtime/runtime1.go
index 2132ceecb2..5beaa4dd74 100644
--- a/src/runtime/runtime1.go
+++ b/src/runtime/runtime1.go
@@ -8,6 +8,7 @@ import (
"internal/bytealg"
"internal/goarch"
"internal/runtime/atomic"
+ "internal/runtime/strconv"
"unsafe"
)
@@ -526,13 +527,13 @@ func parsegodebug(godebug string, seen map[string]bool) {
// is int, not int32, and should only be updated
// if specified in GODEBUG.
if seen == nil && key == "memprofilerate" {
- if n, ok := atoi(value); ok {
+ if n, ok := strconv.Atoi(value); ok {
MemProfileRate = n
}
} else {
for _, v := range dbgvars {
if v.name == key {
- if n, ok := atoi32(value); ok {
+ if n, ok := strconv.Atoi32(value); ok {
if seen == nil && v.value != nil {
*v.value = n
} else if v.atomic != nil {
@@ -572,7 +573,7 @@ func setTraceback(level string) {
fallthrough
default:
t = tracebackAll
- if n, ok := atoi(level); ok && n == int(uint32(n)) {
+ if n, ok := strconv.Atoi(level); ok && n == int(uint32(n)) {
t |= uint32(n) << tracebackShift
}
}
diff --git a/src/runtime/string.go b/src/runtime/string.go
index 7bb9d58de0..44d586bc53 100644
--- a/src/runtime/string.go
+++ b/src/runtime/string.go
@@ -8,6 +8,8 @@ import (
"internal/abi"
"internal/bytealg"
"internal/goarch"
+ "internal/runtime/math"
+ "internal/runtime/strconv"
"internal/runtime/sys"
"unsafe"
)
@@ -391,77 +393,6 @@ func gostringn(p *byte, l int) string {
return s
}
-const (
- maxUint64 = ^uint64(0)
- maxInt64 = int64(maxUint64 >> 1)
-)
-
-// atoi64 parses an int64 from a string s.
-// The bool result reports whether s is a number
-// representable by a value of type int64.
-func atoi64(s string) (int64, bool) {
- if s == "" {
- return 0, false
- }
-
- neg := false
- if s[0] == '-' {
- neg = true
- s = s[1:]
- }
-
- un := uint64(0)
- for i := 0; i < len(s); i++ {
- c := s[i]
- if c < '0' || c > '9' {
- return 0, false
- }
- if un > maxUint64/10 {
- // overflow
- return 0, false
- }
- un *= 10
- un1 := un + uint64(c) - '0'
- if un1 < un {
- // overflow
- return 0, false
- }
- un = un1
- }
-
- if !neg && un > uint64(maxInt64) {
- return 0, false
- }
- if neg && un > uint64(maxInt64)+1 {
- return 0, false
- }
-
- n := int64(un)
- if neg {
- n = -n
- }
-
- return n, true
-}
-
-// atoi is like atoi64 but for integers
-// that fit into an int.
-func atoi(s string) (int, bool) {
- if n, ok := atoi64(s); n == int64(int(n)) {
- return int(n), ok
- }
- return 0, false
-}
-
-// atoi32 is like atoi but for integers
-// that fit into an int32.
-func atoi32(s string) (int32, bool) {
- if n, ok := atoi64(s); n == int64(int32(n)) {
- return int32(n), ok
- }
- return 0, false
-}
-
// parseByteCount parses a string that represents a count of bytes.
//
// s must match the following regular expression:
@@ -483,7 +414,7 @@ func parseByteCount(s string) (int64, bool) {
// Handle the easy non-suffix case.
last := s[len(s)-1]
if last >= '0' && last <= '9' {
- n, ok := atoi64(s)
+ n, ok := strconv.Atoi64(s)
if !ok || n < 0 {
return 0, false
}
@@ -498,7 +429,7 @@ func parseByteCount(s string) (int64, bool) {
// The one before that must always be a digit or 'i'.
if c := s[len(s)-2]; c >= '0' && c <= '9' {
// Trivial 'B' suffix.
- n, ok := atoi64(s[:len(s)-1])
+ n, ok := strconv.Atoi64(s[:len(s)-1])
if !ok || n < 0 {
return 0, false
}
@@ -529,17 +460,17 @@ func parseByteCount(s string) (int64, bool) {
for i := 0; i < power; i++ {
m *= 1024
}
- n, ok := atoi64(s[:len(s)-3])
+ n, ok := strconv.Atoi64(s[:len(s)-3])
if !ok || n < 0 {
return 0, false
}
un := uint64(n)
- if un > maxUint64/m {
+ if un > math.MaxUint64/m {
// Overflow.
return 0, false
}
un *= m
- if un > uint64(maxInt64) {
+ if un > uint64(math.MaxInt64) {
// Overflow.
return 0, false
}
diff --git a/src/runtime/string_test.go b/src/runtime/string_test.go
index cfc0ad7cde..522a502a1c 100644
--- a/src/runtime/string_test.go
+++ b/src/runtime/string_test.go
@@ -390,98 +390,6 @@ func TestString2Slice(t *testing.T) {
const intSize = 32 << (^uint(0) >> 63)
-type atoi64Test struct {
- in string
- out int64
- ok bool
-}
-
-var atoi64tests = []atoi64Test{
- {"", 0, false},
- {"0", 0, true},
- {"-0", 0, true},
- {"1", 1, true},
- {"-1", -1, true},
- {"12345", 12345, true},
- {"-12345", -12345, true},
- {"012345", 12345, true},
- {"-012345", -12345, true},
- {"12345x", 0, false},
- {"-12345x", 0, false},
- {"98765432100", 98765432100, true},
- {"-98765432100", -98765432100, true},
- {"20496382327982653440", 0, false},
- {"-20496382327982653440", 0, false},
- {"9223372036854775807", 1<<63 - 1, true},
- {"-9223372036854775807", -(1<<63 - 1), true},
- {"9223372036854775808", 0, false},
- {"-9223372036854775808", -1 << 63, true},
- {"9223372036854775809", 0, false},
- {"-9223372036854775809", 0, false},
-}
-
-func TestAtoi(t *testing.T) {
- switch intSize {
- case 32:
- for i := range atoi32tests {
- test := &atoi32tests[i]
- out, ok := runtime.Atoi(test.in)
- if test.out != int32(out) || test.ok != ok {
- t.Errorf("atoi(%q) = (%v, %v) want (%v, %v)",
- test.in, out, ok, test.out, test.ok)
- }
- }
- case 64:
- for i := range atoi64tests {
- test := &atoi64tests[i]
- out, ok := runtime.Atoi(test.in)
- if test.out != int64(out) || test.ok != ok {
- t.Errorf("atoi(%q) = (%v, %v) want (%v, %v)",
- test.in, out, ok, test.out, test.ok)
- }
- }
- }
-}
-
-type atoi32Test struct {
- in string
- out int32
- ok bool
-}
-
-var atoi32tests = []atoi32Test{
- {"", 0, false},
- {"0", 0, true},
- {"-0", 0, true},
- {"1", 1, true},
- {"-1", -1, true},
- {"12345", 12345, true},
- {"-12345", -12345, true},
- {"012345", 12345, true},
- {"-012345", -12345, true},
- {"12345x", 0, false},
- {"-12345x", 0, false},
- {"987654321", 987654321, true},
- {"-987654321", -987654321, true},
- {"2147483647", 1<<31 - 1, true},
- {"-2147483647", -(1<<31 - 1), true},
- {"2147483648", 0, false},
- {"-2147483648", -1 << 31, true},
- {"2147483649", 0, false},
- {"-2147483649", 0, false},
-}
-
-func TestAtoi32(t *testing.T) {
- for i := range atoi32tests {
- test := &atoi32tests[i]
- out, ok := runtime.Atoi32(test.in)
- if test.out != out || test.ok != ok {
- t.Errorf("atoi32(%q) = (%v, %v) want (%v, %v)",
- test.in, out, ok, test.out, test.ok)
- }
- }
-}
-
func TestParseByteCount(t *testing.T) {
for _, test := range []struct {
in string