diff --git a/go.mod b/go.mod index 55334bdd..fb6dfa90 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/go-ruby-erb/erb v0.0.0-20260629074717-0999ae4dd529 github.com/go-ruby-faker/faker v0.0.0-20260630192057-0a0efdf75352 github.com/go-ruby-find/find v0.0.0-20260630081030-35072d185272 - github.com/go-ruby-format/format v0.0.0-20260629094031-e294417ab31c + github.com/go-ruby-format/format v0.0.0-20260703115518-8adcf1b4af5f github.com/go-ruby-getoptlong/getoptlong v0.0.0-20260629150025-1a1bfd19bc49 github.com/go-ruby-grape/grape v0.0.0-20260702151528-455377c8c7c3 github.com/go-ruby-haml/haml v0.0.0-20260701125233-5bf8084caf1c @@ -60,7 +60,7 @@ require ( github.com/go-ruby-pathname/pathname v0.0.0-20260629151955-d8d2c4e5f81b github.com/go-ruby-pg/pg v0.0.0-20260702135906-e5650264cc5d github.com/go-ruby-prettyprint/prettyprint v0.0.0-20260629152429-60a380e82d7d - github.com/go-ruby-prime/prime v0.0.0-20260629151744-aa26747e21a8 + github.com/go-ruby-prime/prime v0.0.0-20260703101152-9274dca78d88 github.com/go-ruby-pstore/pstore v0.0.0-20260630081017-0dd55a12f94e github.com/go-ruby-public-suffix/public-suffix v0.0.0-20260630151503-f308d4002444 github.com/go-ruby-rack/rack v0.0.0-20260630124607-8a4f9cfcdbb5 @@ -88,7 +88,7 @@ require ( github.com/go-ruby-uri/uri v0.0.0-20260629113958-59633d1b0deb github.com/go-ruby-xslt/xslt v0.0.0-20260702171958-146eaf3f0176 github.com/go-ruby-yaml/yaml v0.0.0-20260629093916-8035038027bd - github.com/go-ruby-zlib/zlib v0.0.0-20260630052127-b4ac0c1ab281 + github.com/go-ruby-zlib/zlib v0.0.0-20260703114753-dbf176c108fc ) require ( @@ -99,6 +99,7 @@ require ( github.com/go-simd/base64 v0.0.0-20260627054622-4058a7093f4c // indirect github.com/go-simd/hex v0.0.0-20260627054622-d04d429c6aea // indirect github.com/google/uuid v1.6.0 // indirect + github.com/klauspost/compress v1.18.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/ncruces/go-strftime v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect diff --git a/go.sum b/go.sum index 83832d07..077bd47e 100644 --- a/go.sum +++ b/go.sum @@ -72,8 +72,8 @@ github.com/go-ruby-faker/faker v0.0.0-20260630192057-0a0efdf75352 h1:cRiGBGZWF8P github.com/go-ruby-faker/faker v0.0.0-20260630192057-0a0efdf75352/go.mod h1:7h2mxQ3mnWz9bg39nSDr0h/LxqnEnbrJxmNRDbu3zs8= github.com/go-ruby-find/find v0.0.0-20260630081030-35072d185272 h1:tHmlER04rO8LxFWbDiKqcFDzB4WFzBio/c6kgeOdpGo= github.com/go-ruby-find/find v0.0.0-20260630081030-35072d185272/go.mod h1:wpq0ciEFn8zUc1vfuHRjF2U8mH0krhJCRHHr+JOGaEY= -github.com/go-ruby-format/format v0.0.0-20260629094031-e294417ab31c h1:dJBMxMFxBLrk4LoWL23tOl5M8P0CA8Gb7r/mvsaPk6A= -github.com/go-ruby-format/format v0.0.0-20260629094031-e294417ab31c/go.mod h1:1tuz12SHPOcnFSfdPYRFwVvRhMT4Xn4M4jfPFgM7pLA= +github.com/go-ruby-format/format v0.0.0-20260703115518-8adcf1b4af5f h1:N4nvZMNtLocbwcjJiXIhZ0gq8r0fUc7J3F/U2hnDMYQ= +github.com/go-ruby-format/format v0.0.0-20260703115518-8adcf1b4af5f/go.mod h1:1tuz12SHPOcnFSfdPYRFwVvRhMT4Xn4M4jfPFgM7pLA= github.com/go-ruby-getoptlong/getoptlong v0.0.0-20260629150025-1a1bfd19bc49 h1:v2Cy3bZTTi+u9XKBRP8FmYHwsWwZ+t8hlh3s4i1gGfQ= github.com/go-ruby-getoptlong/getoptlong v0.0.0-20260629150025-1a1bfd19bc49/go.mod h1:nGvVSBSIsYrhM2qs4yQ2k8uDbXCFPWQXXqkowleHMDs= github.com/go-ruby-grape/grape v0.0.0-20260702151528-455377c8c7c3 h1:JPcmRUa/fiwj55H7LXULvrxifCaQkH6fXTvDZZg3pvw= @@ -128,8 +128,8 @@ github.com/go-ruby-pg/pg v0.0.0-20260702135906-e5650264cc5d h1:JKIZD+pJQ64/nla4D github.com/go-ruby-pg/pg v0.0.0-20260702135906-e5650264cc5d/go.mod h1:XIYL8wT1Thvoeh7y5v9ZrL+34ySHiieu1/GyDZauULk= github.com/go-ruby-prettyprint/prettyprint v0.0.0-20260629152429-60a380e82d7d h1:hH6O6QdrUlbePkqb8skhFQ0BVuMe0u1c/CXFFaVUnXg= github.com/go-ruby-prettyprint/prettyprint v0.0.0-20260629152429-60a380e82d7d/go.mod h1:SIQyfiIJRKJq0OD9gWYfmvQTdJClJf8z56BsfVVw7rY= -github.com/go-ruby-prime/prime v0.0.0-20260629151744-aa26747e21a8 h1:zghkWpebo93z7Uz/BMXnLSN30ZPeLXS35Y1HPeXkUE4= -github.com/go-ruby-prime/prime v0.0.0-20260629151744-aa26747e21a8/go.mod h1:dG/91hk8HlQ6YAdD64xiMFHTTmXjTWtapKWoG9JtcRA= +github.com/go-ruby-prime/prime v0.0.0-20260703101152-9274dca78d88 h1:td+zp+GfoRSqG1kuNvKiX+ujC6QQwR5YsQjqeXFUofg= +github.com/go-ruby-prime/prime v0.0.0-20260703101152-9274dca78d88/go.mod h1:dG/91hk8HlQ6YAdD64xiMFHTTmXjTWtapKWoG9JtcRA= github.com/go-ruby-pstore/pstore v0.0.0-20260630081017-0dd55a12f94e h1:JmbJL+0nq6HY1SX1tDBFQyo7M8JgbQ+ukwn8iLyNGfs= github.com/go-ruby-pstore/pstore v0.0.0-20260630081017-0dd55a12f94e/go.mod h1:tbYmHYXjnNXJCNYABDx9nWfuGBCXeKrp134bNsQeTpM= github.com/go-ruby-public-suffix/public-suffix v0.0.0-20260630151503-f308d4002444 h1:vkMPJDpZHmu09pVxr7CFppoh1b5Kc8LkhKpxkSBoyxA= @@ -184,8 +184,8 @@ github.com/go-ruby-xslt/xslt v0.0.0-20260702171958-146eaf3f0176 h1:iHTCF/Eu0/lwO github.com/go-ruby-xslt/xslt v0.0.0-20260702171958-146eaf3f0176/go.mod h1:1C3Bx0pQIsqLqC2SnqV7pJnGuLJsgYmar2NrD+CmVXw= github.com/go-ruby-yaml/yaml v0.0.0-20260629093916-8035038027bd h1:4HGjgCdTIuIEM+iZXJ9hJJzt0IBMtxPqdJq72mr0Y64= github.com/go-ruby-yaml/yaml v0.0.0-20260629093916-8035038027bd/go.mod h1:HtbQgnZpSTJBAhPg1jOKgHycZl3tXgf9ZdOu4dnHWjQ= -github.com/go-ruby-zlib/zlib v0.0.0-20260630052127-b4ac0c1ab281 h1:zOn3ro0WBggDR0xqvNSdOPxHEWzdR6iMy842CAyIL/g= -github.com/go-ruby-zlib/zlib v0.0.0-20260630052127-b4ac0c1ab281/go.mod h1:abizNd4NW4jTmJYNmfSIuLz0J3l7MAImkcZKlslLNRM= +github.com/go-ruby-zlib/zlib v0.0.0-20260703114753-dbf176c108fc h1:Gap/pSbjMktFKJQ4tcSmmL0JKBEQZ4TK/bEZ49pUp60= +github.com/go-ruby-zlib/zlib v0.0.0-20260703114753-dbf176c108fc/go.mod h1:WGW5hh1yjgYm98zeoraLu8DWJ0iRFSU0fNmK8PXQ43M= github.com/go-simd/base64 v0.0.0-20260627054622-4058a7093f4c h1:iA1vHw+pR44TUtDlviXz3P8pAExO1nSm39PMZ7MsuYU= github.com/go-simd/base64 v0.0.0-20260627054622-4058a7093f4c/go.mod h1:/nL/YHcchLkENbcFEEqAcfESIiwCM9/1H67LsSnd7EI= github.com/go-simd/hex v0.0.0-20260627054622-d04d429c6aea h1:qVh9jV1QriPPfKtAb8au5pmVxKDk5Vl4nQUi/issQM0= @@ -200,6 +200,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/klauspost/compress v1.18.7 h1:aUyZsS4kH3QTKurYhAOwAHxllVPnOthb3vPfnF1Ehjw= +github.com/klauspost/compress v1.18.7/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= diff --git a/internal/vm/format_bind.go b/internal/vm/format_bind.go index 57c13fdf..4ea07dcf 100644 --- a/internal/vm/format_bind.go +++ b/internal/vm/format_bind.go @@ -53,6 +53,25 @@ func (fv formatValue) Kind() format.Kind { } } +// Int64Fast reports a genuine int64-range Ruby Integer without allocating a +// *big.Int, letting the library's integer conversions (%d/%x/%o/%b/…) skip +// math/big for the common small-integer case. A Bignum reports its int64 value +// only when it fits; every other value (Float, String, etc.) reports ok=false so +// the formatter uses the precise Int() path with its coercion and error rules. +func (fv formatValue) Int64Fast() (int64, bool) { + switch x := fv.v.(type) { + case object.Integer: + return int64(x), true + case *object.Bignum: + if x.I.IsInt64() { + return x.I.Int64(), true + } + return 0, false + default: + return 0, false + } +} + // ToS is the Ruby to_s rendering (%s and the textual value of %{name}). func (fv formatValue) ToS() string { return fv.v.ToS() } @@ -173,9 +192,13 @@ func formatNamedArgs(args []object.Value) *format.NamedArgs { // single entry point Kernel#sprintf / Kernel#format / IO#printf / String#% all // funnel through, so their formatting behaviour is identical. func formatString(fmtStr string, args []object.Value) string { + // One backing array of wrappers, referenced by pointer, so wrapping N + // operands costs a single allocation instead of one interface box per arg. + wraps := make([]formatValue, len(args)) vals := make([]format.Value, len(args)) for i, a := range args { - vals[i] = formatValue{a} + wraps[i].v = a + vals[i] = &wraps[i] } out, err := format.Format(fmtStr, vals, formatNamedArgs(args)) if err != nil { diff --git a/internal/vm/format_bind_internal_test.go b/internal/vm/format_bind_internal_test.go index 3f4757d9..121aaf1e 100644 --- a/internal/vm/format_bind_internal_test.go +++ b/internal/vm/format_bind_internal_test.go @@ -39,6 +39,31 @@ func TestFormatValueKind(t *testing.T) { } } +// TestFormatValueInt64Fast covers every arm of formatValue.Int64Fast, the +// allocation-free fast path the library's integer conversions take: a native +// Integer and an int64-range Bignum report their value; an out-of-range Bignum +// and any non-integer decline so the formatter uses the precise Int() path. +func TestFormatValueInt64Fast(t *testing.T) { + big64 := new(big.Int).Lsh(big.NewInt(1), 100) // 2^100, far beyond int64 + cases := []struct { + v object.Value + wantN int64 + wantOK bool + }{ + {object.Integer(-42), -42, true}, + {&object.Bignum{I: big.NewInt(7)}, 7, true}, // Bignum that fits int64 + {&object.Bignum{I: big64}, 0, false}, // Bignum exceeding int64 + {object.Float(1.5), 0, false}, // non-integer declines + {object.NewString("9"), 0, false}, // String declines (Int() parses it) + } + for _, c := range cases { + n, ok := (formatValue{c.v}).Int64Fast() + if n != c.wantN || ok != c.wantOK { + t.Errorf("Int64Fast(%T=%v) = (%d, %v), want (%d, %v)", c.v, c.v, n, ok, c.wantN, c.wantOK) + } + } +} + // TestFormatValueInspect covers formatValue.Inspect (the %p backing), which the // library calls when rendering an inspected value. func TestFormatValueInspect(t *testing.T) {