@@ -114,19 +114,19 @@ mod generic;
114114#[ cfg( target_arch = "x86_64" ) ]
115115mod x86;
116116
117- pub use generic:: escape_generic;
117+ pub use generic:: { escape_generic, escape_into_generic } ;
118118
119119/// Main entry point for JSON string escaping with SIMD acceleration
120120/// If the platform is supported, the SIMD path will be used. Otherwise, the generic fallback will be used.
121121pub fn escape < S : AsRef < str > > ( input : S ) -> String {
122+ use generic:: escape_inner;
123+ 124+ let mut result = Vec :: with_capacity ( input. as_ref ( ) . len ( ) + input. as_ref ( ) . len ( ) / 2 + 2 ) ;
125+ result. push ( b'"' ) ;
126+ let s = input. as_ref ( ) ;
127+ let bytes = s. as_bytes ( ) ;
122128 #[ cfg( target_arch = "x86_64" ) ]
123129 {
124- use generic:: escape_inner;
125- 126- let mut result = Vec :: with_capacity ( input. as_ref ( ) . len ( ) + input. as_ref ( ) . len ( ) / 2 + 2 ) ;
127- result. push ( b'"' ) ;
128- let s = input. as_ref ( ) ;
129- let bytes = s. as_bytes ( ) ;
130130 let len = bytes. len ( ) ;
131131 // Runtime CPU feature detection for x86_64
132132 if is_x86_feature_detected ! ( "avx512f" )
@@ -153,7 +153,7 @@ pub fn escape<S: AsRef<str>>(input: S) -> String {
153153 {
154154 #[ cfg( feature = "force_aarch64_neon" ) ]
155155 {
156- return aarch64:: escape_neon ( input ) ;
156+ return aarch64:: escape_neon ( bytes , & mut result ) ;
157157 }
158158 #[ cfg( not( feature = "force_aarch64_neon" ) ) ]
159159 {
@@ -162,17 +162,74 @@ pub fn escape<S: AsRef<str>>(input: S) -> String {
162162 // TODO: add support for sve2 chips with wider registers
163163 // github actions ubuntu-24.04-arm runner has 128 bits sve2 registers, it's not enough for the SIMD path
164164 if cfg ! ( target_os = "macos" ) && std:: arch:: is_aarch64_feature_detected!( "bf16" ) {
165- return aarch64:: escape_neon ( input ) ;
165+ aarch64:: escape_neon ( bytes , & mut result ) ;
166166 } else {
167- return escape_generic ( input ) ;
167+ escape_inner ( bytes , & mut result ) ;
168168 }
169+ result. push ( b'"' ) ;
170+ // SAFETY: We only pushed valid UTF-8 bytes (original string bytes and ASCII escape sequences)
171+ unsafe { String :: from_utf8_unchecked ( result) }
169172 }
170173 }
171174
172175 #[ cfg( not( any( target_arch = "x86_64" , target_arch = "aarch64" ) ) ) ]
173176 escape_generic ( input)
174177}
175178
179+ /// Main entry point for JSON string escaping with SIMD acceleration
180+ /// If the platform is supported, the SIMD path will be used. Otherwise, the generic fallback will be used.
181+ pub fn escape_into < S : AsRef < str > > ( input : S , output : & mut Vec < u8 > ) {
182+ use generic:: escape_inner;
183+ 184+ output. push ( b'"' ) ;
185+ let s = input. as_ref ( ) ;
186+ let bytes = s. as_bytes ( ) ;
187+ #[ cfg( target_arch = "x86_64" ) ]
188+ {
189+ let len = bytes. len ( ) ;
190+ // Runtime CPU feature detection for x86_64
191+ if is_x86_feature_detected ! ( "avx512f" )
192+ && is_x86_feature_detected ! ( "avx512bw" )
193+ && len >= x86:: LOOP_SIZE_AVX512
194+ {
195+ unsafe { x86:: escape_avx512 ( bytes, output) }
196+ } else if is_x86_feature_detected ! ( "avx2" ) && len >= x86:: LOOP_SIZE_AVX2 {
197+ unsafe { x86:: escape_avx2 ( bytes, output) }
198+ } else if is_x86_feature_detected ! ( "sse2" )
199+ && /* if len < 128, no need to use simd */
200+ len >= x86:: LOOP_SIZE_AVX2
201+ {
202+ unsafe { x86:: escape_sse2 ( bytes, output) }
203+ } else {
204+ escape_inner ( bytes, output) ;
205+ }
206+ output. push ( b'"' ) ;
207+ }
208+ 209+ #[ cfg( target_arch = "aarch64" ) ]
210+ {
211+ #[ cfg( feature = "force_aarch64_neon" ) ]
212+ {
213+ return aarch64:: escape_neon ( bytes, output) ;
214+ }
215+ #[ cfg( not( feature = "force_aarch64_neon" ) ) ]
216+ {
217+ // on Apple M2 and later, the `bf16` feature is available
218+ // it means they have more registers and can significantly benefit from the SIMD path
219+ // TODO: add support for sve2 chips with wider registers
220+ // github actions ubuntu-24.04-arm runner has 128 bits sve2 registers, it's not enough for the SIMD path
221+ if cfg ! ( target_os = "macos" ) && std:: arch:: is_aarch64_feature_detected!( "bf16" ) {
222+ aarch64:: escape_neon ( bytes, output) ;
223+ } else {
224+ escape_inner ( bytes, output) ;
225+ }
226+ }
227+ }
228+ 229+ #[ cfg( not( any( target_arch = "x86_64" , target_arch = "aarch64" ) ) ) ]
230+ escape_into_generic ( input, output) ;
231+ }
232+ 176233#[ test]
177234fn test_escape_ascii_json_string ( ) {
178235 let fixture = r#"abcdefghijklmnopqrstuvwxyz .*? hello world escape json string"# ;
@@ -377,6 +434,9 @@ fn test_rxjs() {
377434 assert ! ( !sources. is_empty( ) ) ;
378435 for source in sources {
379436 assert_eq ! ( escape( & source) , serde_json:: to_string( & source) . unwrap( ) ) ;
437+ let mut output = String :: new ( ) ;
438+ escape_into ( & source, unsafe { output. as_mut_vec ( ) } ) ;
439+ assert_eq ! ( output, serde_json:: to_string( & source) . unwrap( ) ) ;
380440 }
381441}
382442
@@ -402,5 +462,8 @@ fn test_sources() {
402462 assert ! ( !sources. is_empty( ) ) ;
403463 for source in sources {
404464 assert_eq ! ( escape( & source) , serde_json:: to_string( & source) . unwrap( ) ) ;
465+ let mut output = String :: new ( ) ;
466+ escape_into ( & source, unsafe { output. as_mut_vec ( ) } ) ;
467+ assert_eq ! ( output, serde_json:: to_string( & source) . unwrap( ) ) ;
405468 }
406469}
0 commit comments