Skip to content

Commit 7bc65bc

Browse files
perf(iconv): EUC-JP encode direct table — 2.39x, now beats glibc 2.1x
encode_eucjp was the one DBCS encoder still doing a per-char binary_search over EUC_JP_ENC (13,169 entries, ~14 cache-missing probes); every other DBCS encoder was converted to a direct table in 6750bb6 but EUC-JP has 3-byte (0x8F-plane) packed forms that the generic encode_dbcs2 doesn't handle, so it was missed. Replace with a lazy OnceLock direct cp->packed table ([u32;0x10000], packed+1 so 0=unrepresentable); every EUC-JP-encodable cp is BMP (max key 0xFFE5) so the table is exhaustive and byte-identical to the binary search. UTF-8->EUC-JP encode: 11.413 -> 4.770 ns/B = 2.39x self-speedup; vs glibc 1.26x SLOWER -> 0.47x (2.1x FASTER than glibc, glibc 10.07 ns/B). Isomorphism: iconv_differential_fuzz (4 tests, 0 divergences vs host glibc; iconv_cjk_differential_fuzz_vs_glibc exercises UTF-8<->EUC-JP byte-for-byte + errno + inbytesleft over 6000 CJK-biased inputs). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 6750bb6 commit 7bc65bc

1 file changed

Lines changed: 40 additions & 26 deletions

File tree

  • crates/frankenlibc-core/src/iconv

crates/frankenlibc-core/src/iconv/mod.rs

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8433,34 +8433,48 @@ fn decode_eucjp(input: &[u8]) -> Result<(char, usize), DecodeError> {
84338433
/// 3 bytes `(b0<<16)|(b1<<8)|b2` (the 0x8F plane). Absent code points are
84348434
/// `Unrepresentable` (EILSEQ).
84358435
fn encode_eucjp(ch: char, out: &mut [u8]) -> Result<usize, EncodeError> {
8436-
let cp = ch as u32;
8437-
match cjk_tables::EUC_JP_ENC.binary_search_by_key(&cp, |&(c, _)| c) {
8438-
Ok(i) => {
8439-
let packed = cjk_tables::EUC_JP_ENC[i].1;
8440-
if packed < 0x100 {
8441-
if out.is_empty() {
8442-
return Err(EncodeError::NoSpace);
8443-
}
8444-
out[0] = packed as u8;
8445-
Ok(1)
8446-
} else if packed < 0x1_0000 {
8447-
if out.len() < 2 {
8448-
return Err(EncodeError::NoSpace);
8449-
}
8450-
out[0] = (packed >> 8) as u8;
8451-
out[1] = (packed & 0xFF) as u8;
8452-
Ok(2)
8453-
} else {
8454-
if out.len() < 3 {
8455-
return Err(EncodeError::NoSpace);
8456-
}
8457-
out[0] = (packed >> 16) as u8;
8458-
out[1] = ((packed >> 8) & 0xFF) as u8;
8459-
out[2] = (packed & 0xFF) as u8;
8460-
Ok(3)
8436+
// Direct `code point -> packed encoding` table (`[u32; 0x10000]`, stored as
8437+
// `packed + 1` so 0 means unrepresentable; a `packed` of 0 is a valid single
8438+
// 0x00 byte so it cannot itself be the sentinel). Replaces a per-char binary
8439+
// search over 13,169 entries (~14 cache-missing probes) with one O(1) index.
8440+
// Every EUC-JP-encodable code point is BMP (`EUC_JP_ENC` max key 0xFFE5), so
8441+
// the BMP-sized table is exhaustive — identical results to the binary search.
8442+
static ENC_DIRECT: std::sync::OnceLock<Vec<u32>> = std::sync::OnceLock::new();
8443+
let direct = ENC_DIRECT.get_or_init(|| {
8444+
let mut t = vec![0u32; 0x10000];
8445+
for &(cp, packed) in cjk_tables::EUC_JP_ENC.iter() {
8446+
if cp < 0x10000 {
8447+
t[cp as usize] = packed + 1;
84618448
}
84628449
}
8463-
Err(_) => Err(EncodeError::Unrepresentable),
8450+
t
8451+
});
8452+
let cp = ch as u32;
8453+
if cp >= 0x10000 || direct[cp as usize] == 0 {
8454+
return Err(EncodeError::Unrepresentable);
8455+
}
8456+
let packed = direct[cp as usize] - 1;
8457+
if packed < 0x100 {
8458+
if out.is_empty() {
8459+
return Err(EncodeError::NoSpace);
8460+
}
8461+
out[0] = packed as u8;
8462+
Ok(1)
8463+
} else if packed < 0x1_0000 {
8464+
if out.len() < 2 {
8465+
return Err(EncodeError::NoSpace);
8466+
}
8467+
out[0] = (packed >> 8) as u8;
8468+
out[1] = (packed & 0xFF) as u8;
8469+
Ok(2)
8470+
} else {
8471+
if out.len() < 3 {
8472+
return Err(EncodeError::NoSpace);
8473+
}
8474+
out[0] = (packed >> 16) as u8;
8475+
out[1] = ((packed >> 8) & 0xFF) as u8;
8476+
out[2] = (packed & 0xFF) as u8;
8477+
Ok(3)
84648478
}
84658479
}
84668480

0 commit comments

Comments
 (0)