@@ -114,19 +114,19 @@ mod generic;
114
114
#[ cfg( target_arch = "x86_64" ) ]
115
115
mod x86;
116
116
117
- pub use generic:: escape_generic;
117
+ pub use generic:: { escape_generic, escape_into_generic } ;
118
118
119
119
/// Main entry point for JSON string escaping with SIMD acceleration
120
120
/// If the platform is supported, the SIMD path will be used. Otherwise, the generic fallback will be used.
121
121
pub 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 ( ) ;
122
128
#[ cfg( target_arch = "x86_64" ) ]
123
129
{
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 ( ) ;
130
130
let len = bytes. len ( ) ;
131
131
// Runtime CPU feature detection for x86_64
132
132
if is_x86_feature_detected ! ( "avx512f" )
@@ -153,7 +153,7 @@ pub fn escape<S: AsRef<str>>(input: S) -> String {
153
153
{
154
154
#[ cfg( feature = "force_aarch64_neon" ) ]
155
155
{
156
- return aarch64:: escape_neon ( input ) ;
156
+ return aarch64:: escape_neon ( bytes , & mut result ) ;
157
157
}
158
158
#[ cfg( not( feature = "force_aarch64_neon" ) ) ]
159
159
{
@@ -162,17 +162,74 @@ pub fn escape<S: AsRef<str>>(input: S) -> String {
162
162
// TODO: add support for sve2 chips with wider registers
163
163
// github actions ubuntu-24.04-arm runner has 128 bits sve2 registers, it's not enough for the SIMD path
164
164
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 ) ;
166
166
} else {
167
- return escape_generic ( input ) ;
167
+ escape_inner ( bytes , & mut result ) ;
168
168
}
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) }
169
172
}
170
173
}
171
174
172
175
#[ cfg( not( any( target_arch = "x86_64" , target_arch = "aarch64" ) ) ) ]
173
176
escape_generic ( input)
174
177
}
175
178
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
+
176
233
#[ test]
177
234
fn test_escape_ascii_json_string ( ) {
178
235
let fixture = r#"abcdefghijklmnopqrstuvwxyz .*? hello world escape json string"# ;
@@ -377,6 +434,9 @@ fn test_rxjs() {
377
434
assert ! ( !sources. is_empty( ) ) ;
378
435
for source in sources {
379
436
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( ) ) ;
380
440
}
381
441
}
382
442
@@ -402,5 +462,8 @@ fn test_sources() {
402
462
assert ! ( !sources. is_empty( ) ) ;
403
463
for source in sources {
404
464
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( ) ) ;
405
468
}
406
469
}
0 commit comments