@@ -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" )
@@ -144,16 +144,71 @@ pub fn escape<S: AsRef<str>>(input: S) -> String {
144144 } else {
145145 escape_inner ( bytes, & mut result) ;
146146 }
147- result. push ( b'"' ) ;
148- // SAFETY: We only pushed valid UTF-8 bytes (original string bytes and ASCII escape sequences)
149- unsafe { String :: from_utf8_unchecked ( result) }
150147 }
151148
152149 #[ cfg( target_arch = "aarch64" ) ]
153150 {
154151 #[ cfg( feature = "force_aarch64_neon" ) ]
155152 {
156- return aarch64:: escape_neon ( input) ;
153+ aarch64:: escape_neon ( bytes, & mut result) ;
154+ }
155+ #[ cfg( not( feature = "force_aarch64_neon" ) ) ]
156+ {
157+ // on Apple M2 and later, the `bf16` feature is available
158+ // it means they have more registers and can significantly benefit from the SIMD path
159+ // TODO: add support for sve2 chips with wider registers
160+ // github actions ubuntu-24.04-arm runner has 128 bits sve2 registers, it's not enough for the SIMD path
161+ if cfg ! ( target_os = "macos" ) && std:: arch:: is_aarch64_feature_detected!( "bf16" ) {
162+ aarch64:: escape_neon ( bytes, & mut result) ;
163+ } else {
164+ escape_inner ( bytes, & mut result) ;
165+ }
166+ }
167+ }
168+ 169+ #[ cfg( not( any( target_arch = "x86_64" , target_arch = "aarch64" ) ) ) ]
170+ {
171+ escape_inner ( bytes, & mut result) ;
172+ }
173+ result. push ( b'"' ) ;
174+ // SAFETY: We only pushed valid UTF-8 bytes (original string bytes and ASCII escape sequences)
175+ unsafe { String :: from_utf8_unchecked ( result) }
176+ }
177+ 178+ /// Main entry point for JSON string escaping with SIMD acceleration
179+ /// If the platform is supported, the SIMD path will be used. Otherwise, the generic fallback will be used.
180+ pub fn escape_into < S : AsRef < str > > ( input : S , output : & mut Vec < u8 > ) {
181+ use generic:: escape_inner;
182+ 183+ output. push ( b'"' ) ;
184+ let s = input. as_ref ( ) ;
185+ let bytes = s. as_bytes ( ) ;
186+ #[ cfg( target_arch = "x86_64" ) ]
187+ {
188+ let len = bytes. len ( ) ;
189+ // Runtime CPU feature detection for x86_64
190+ if is_x86_feature_detected ! ( "avx512f" )
191+ && is_x86_feature_detected ! ( "avx512bw" )
192+ && len >= x86:: LOOP_SIZE_AVX512
193+ {
194+ unsafe { x86:: escape_avx512 ( bytes, output) }
195+ } else if is_x86_feature_detected ! ( "avx2" ) && len >= x86:: LOOP_SIZE_AVX2 {
196+ unsafe { x86:: escape_avx2 ( bytes, output) }
197+ } else if is_x86_feature_detected ! ( "sse2" )
198+ && /* if len < 128, no need to use simd */
199+ len >= x86:: LOOP_SIZE_AVX2
200+ {
201+ unsafe { x86:: escape_sse2 ( bytes, output) }
202+ } else {
203+ escape_inner ( bytes, output) ;
204+ }
205+ }
206+ 207+ #[ cfg( target_arch = "aarch64" ) ]
208+ {
209+ #[ cfg( feature = "force_aarch64_neon" ) ]
210+ {
211+ return aarch64:: escape_neon ( bytes, output) ;
157212 }
158213 #[ cfg( not( feature = "force_aarch64_neon" ) ) ]
159214 {
@@ -162,15 +217,18 @@ pub fn escape<S: AsRef<str>>(input: S) -> String {
162217 // TODO: add support for sve2 chips with wider registers
163218 // github actions ubuntu-24.04-arm runner has 128 bits sve2 registers, it's not enough for the SIMD path
164219 if cfg ! ( target_os = "macos" ) && std:: arch:: is_aarch64_feature_detected!( "bf16" ) {
165- return aarch64:: escape_neon ( input ) ;
220+ aarch64:: escape_neon ( bytes , output ) ;
166221 } else {
167- return escape_generic ( input ) ;
222+ escape_inner ( bytes , output ) ;
168223 }
169224 }
170225 }
171226
172227 #[ cfg( not( any( target_arch = "x86_64" , target_arch = "aarch64" ) ) ) ]
173- escape_generic ( input)
228+ {
229+ escape_into_generic ( input, output) ;
230+ }
231+ output. push ( b'"' ) ;
174232}
175233
176234#[ test]
@@ -377,6 +435,9 @@ fn test_rxjs() {
377435 assert ! ( !sources. is_empty( ) ) ;
378436 for source in sources {
379437 assert_eq ! ( escape( & source) , serde_json:: to_string( & source) . unwrap( ) ) ;
438+ let mut output = String :: new ( ) ;
439+ escape_into ( & source, unsafe { output. as_mut_vec ( ) } ) ;
440+ assert_eq ! ( output, serde_json:: to_string( & source) . unwrap( ) ) ;
380441 }
381442}
382443
@@ -402,5 +463,8 @@ fn test_sources() {
402463 assert ! ( !sources. is_empty( ) ) ;
403464 for source in sources {
404465 assert_eq ! ( escape( & source) , serde_json:: to_string( & source) . unwrap( ) ) ;
466+ let mut output = String :: new ( ) ;
467+ escape_into ( & source, unsafe { output. as_mut_vec ( ) } ) ;
468+ assert_eq ! ( output, serde_json:: to_string( & source) . unwrap( ) ) ;
405469 }
406470}
0 commit comments