From a1f7130b6b8503dd2c02e9f7bb92574ea99851fa Mon Sep 17 00:00:00 2001 From: Cameron Clark Date: Wed, 13 Aug 2025 13:23:12 +0100 Subject: [PATCH 1/2] perf(tspath): avoid string copy in ToFileNameLowerCase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use unsafe.String to convert the lowercase byte slice to a string without an extra allocation and copy, reducing overhead in the ASCII fast path. ``` │ bench-to-file-name-lower-case-BASE.txt │ bench-to-file-name-lower-case-with-unsafe-string.txt │ │ sec/op │ sec/op vs base │ ToFileNameLowerCase//path/to/file.ext-12 8.348n ± ∞ ¹ 8.505n ± ∞ ¹ ~ (p=0.151 n=5) ToFileNameLowerCase//PATH/TO/FILE.EXT-12 37.94n ± ∞ ¹ 27.80n ± ∞ ¹ -26.73% (p=0.008 n=5) ToFileNameLowerCase//path/to/FILE.EXT-12 40.96n ± ∞ ¹ 30.67n ± ∞ ¹ -25.12% (p=0.008 n=5) ToFileNameLowerCase//user/UserName/proje...etc-12 58.11n ± ∞ ¹ 46.78n ± ∞ ¹ -19.50% (p=0.008 n=5) ToFileNameLowerCase//user/UserName/proje...etc#01-12 160.7n ± ∞ ¹ 161.3n ± ∞ ¹ ~ (p=0.286 n=5) ToFileNameLowerCase//user/UserName/proje...etc#02-12 155.5n ± ∞ ¹ 155.9n ± ∞ ¹ ~ (p=0.381 n=5) ToFileNameLowerCase//user/UserName/proje...etc#03-12 138.1n ± ∞ ¹ 137.8n ± ∞ ¹ ~ (p=0.421 n=5) ToFileNameLowerCase/FoO/FoO/FoO/FoO/FoO/...etc-12 543.2n ± ∞ ¹ 515.3n ± ∞ ¹ -5.14% (p=0.008 n=5) geomean 78.30n 70.43n -10.05% ¹ need >= 6 samples for confidence interval at level 0.95 ``` --- internal/tspath/path.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/internal/tspath/path.go b/internal/tspath/path.go index 43d7bb6ea4..1d55392700 100644 --- a/internal/tspath/path.go +++ b/internal/tspath/path.go @@ -4,6 +4,7 @@ import ( "cmp" "strings" "unicode" + "unsafe" "github.com/microsoft/typescript-go/internal/stringutil" ) @@ -606,7 +607,12 @@ func ToFileNameLowerCase(fileName string) string { } b[i] = c } - return string(b) + // SAFETY: `b` is a freshly allocated, non-empty byte slice whose contents are + // fully initialized. We do not mutate `b` after this point, and the returned + // string becomes the only live reference to its backing array. The array is + // heap-allocated (via make), so the GC keeps it alive for the lifetime of the + // returned string. Since len(b) > 0 here, &b[0] is a valid pointer. + return unsafe.String(&b[0], len(b)) } return strings.Map(func(r rune) rune { From f087dffb43c789530f48c47115c4ded55941b795 Mon Sep 17 00:00:00 2001 From: Cameron Clark Date: Fri, 15 Aug 2025 13:08:12 +0100 Subject: [PATCH 2/2] update safety comment --- internal/tspath/path.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/internal/tspath/path.go b/internal/tspath/path.go index 1d55392700..29a6ef8702 100644 --- a/internal/tspath/path.go +++ b/internal/tspath/path.go @@ -607,11 +607,16 @@ func ToFileNameLowerCase(fileName string) string { } b[i] = c } - // SAFETY: `b` is a freshly allocated, non-empty byte slice whose contents are - // fully initialized. We do not mutate `b` after this point, and the returned - // string becomes the only live reference to its backing array. The array is - // heap-allocated (via make), so the GC keeps it alive for the lifetime of the - // returned string. Since len(b) > 0 here, &b[0] is a valid pointer. + // SAFETY: We construct a string that aliases b’s backing array without copying. + // (1) Lifetime: The address of b’s elements escapes via the returned string, + // so escape analysis allocates b’s backing array on the heap. The string + // header points to that heap allocation, ensuring it remains live for the + // string’s lifetime. + // (2) Initialization: We assign to every b[i] before creating the string. + // (Note: Go zeroes all allocated memory, so “uninitialized” bytes cannot occur.) + // (3) Immutability: We do not modify b after this point, so the string view + // observes immutable data. + // (4) Non-empty: On this path len(b) > 0, so &b[0] is a valid, non-nil pointer. return unsafe.String(&b[0], len(b)) }