Skip to content

Commit e786118

Browse files
committed
perf(ext/web): use base64-simd for atob/btoa
1 parent 91570ba commit e786118

File tree

4 files changed

+39
-53
lines changed

4 files changed

+39
-53
lines changed

Cargo.lock

Lines changed: 16 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ opt-level = 3
110110
opt-level = 3
111111
[profile.bench.package.zstd-sys]
112112
opt-level = 3
113+
[profile.bench.package.base64-simd]
114+
opt-level = 3
113115

114116
# NB: the `bench` and `release` profiles must remain EXACTLY the same.
115117
[profile.release.package.rand]
@@ -174,3 +176,5 @@ opt-level = 3
174176
opt-level = 3
175177
[profile.release.package.zstd-sys]
176178
opt-level = 3
179+
[profile.release.package.base64-simd]
180+
opt-level = 3

ext/web/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ path = "lib.rs"
1515

1616
[dependencies]
1717
async-trait = "0.1.51"
18-
base64 = "0.13.0"
18+
base64-simd = "0.6.2"
1919
deno_core = { version = "0.140.0", path = "../../core" }
2020
encoding_rs = "0.8.31"
2121
flate2 = "1"

ext/web/lib.rs

Lines changed: 18 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -124,74 +124,41 @@ pub fn init<P: TimersPermission + 'static>(
124124

125125
#[op]
126126
fn op_base64_decode(input: String) -> Result<ZeroCopyBuf, AnyError> {
127-
let mut input = input.into_bytes();
128-
input.retain(|c| !c.is_ascii_whitespace());
129-
Ok(b64_decode(&input)?.into())
127+
Ok(forgiving_base64_decode(input.into_bytes())?.into())
130128
}
131129

132130
#[op]
133-
fn op_base64_atob(mut s: ByteString) -> Result<ByteString, AnyError> {
134-
s.retain(|c| !c.is_ascii_whitespace());
135-
136-
// If padding is expected, fail if not 4-byte aligned
137-
if s.len() % 4 != 0 && (s.ends_with(b"==") || s.ends_with(b"=")) {
138-
return Err(
139-
DomExceptionInvalidCharacterError::new("Failed to decode base64.").into(),
140-
);
141-
}
142-
143-
Ok(b64_decode(&s)?.into())
131+
fn op_base64_atob(s: ByteString) -> Result<ByteString, AnyError> {
132+
Ok(forgiving_base64_decode(s.into())?.into())
144133
}
145134

146-
fn b64_decode(input: &[u8]) -> Result<Vec<u8>, AnyError> {
147-
// "If the length of input divides by 4 leaving no remainder, then:
148-
// if input ends with one or two U+003D EQUALS SIGN (=) characters,
149-
// remove them from input."
150-
let input = match input.len() % 4 == 0 {
151-
true if input.ends_with(b"==") => &input[..input.len() - 2],
152-
true if input.ends_with(b"=") => &input[..input.len() - 1],
153-
_ => input,
154-
};
135+
/// See <https://infra.spec.whatwg.org/#forgiving-base64>
136+
fn forgiving_base64_decode(mut input: Vec<u8>) -> Result<Vec<u8>, AnyError> {
137+
let error: _ =
138+
|| DomExceptionInvalidCharacterError::new("Failed to decode base64");
155139

156-
// "If the length of input divides by 4 leaving a remainder of 1,
157-
// throw an InvalidCharacterError exception and abort these steps."
158-
if input.len() % 4 == 1 {
159-
return Err(
160-
DomExceptionInvalidCharacterError::new("Failed to decode base64.").into(),
161-
);
162-
}
163-
164-
let cfg = base64::Config::new(base64::CharacterSet::Standard, true)
165-
.decode_allow_trailing_bits(true);
166-
let out = base64::decode_config(input, cfg).map_err(|err| match err {
167-
base64::DecodeError::InvalidByte(_, _) => {
168-
DomExceptionInvalidCharacterError::new(
169-
"Failed to decode base64: invalid character",
170-
)
171-
}
172-
_ => DomExceptionInvalidCharacterError::new(&format!(
173-
"Failed to decode base64: {:?}",
174-
err
175-
)),
176-
})?;
140+
let decoded = base64_simd::Base64::forgiving_decode_inplace(&mut input)
141+
.map_err(|_| error())?;
177142

178-
Ok(out)
143+
let decoded_len = decoded.len();
144+
input.truncate(decoded_len);
145+
Ok(input)
179146
}
180147

181148
#[op]
182149
fn op_base64_encode(s: ZeroCopyBuf) -> String {
183-
b64_encode(&s)
150+
forgiving_base64_encode(s.as_ref())
184151
}
185152

186153
#[op]
187154
fn op_base64_btoa(s: ByteString) -> String {
188-
b64_encode(s)
155+
forgiving_base64_encode(s.as_ref())
189156
}
190157

191-
fn b64_encode(s: impl AsRef<[u8]>) -> String {
192-
let cfg = base64::Config::new(base64::CharacterSet::Standard, true)
193-
.decode_allow_trailing_bits(true);
194-
base64::encode_config(s.as_ref(), cfg)
158+
/// See <https://infra.spec.whatwg.org/#forgiving-base64>
159+
fn forgiving_base64_encode(s: &[u8]) -> String {
160+
let base64 = base64_simd::Base64::STANDARD;
161+
base64.encode_to_boxed_str(s).into_string()
195162
}
196163

197164
#[derive(Deserialize)]

0 commit comments

Comments
 (0)