Skip to content

Commit

Permalink
perf(ext/web): use base64-simd for atob/btoa (#14992)
Browse files Browse the repository at this point in the history
  • Loading branch information
Nugine authored Jun 29, 2022
1 parent 3ad8bd8 commit 1328a56
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 53 deletions.
17 changes: 16 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ opt-level = 3
opt-level = 3
[profile.bench.package.zstd-sys]
opt-level = 3
[profile.bench.package.base64-simd]
opt-level = 3

# NB: the `bench` and `release` profiles must remain EXACTLY the same.
[profile.release.package.rand]
Expand Down Expand Up @@ -174,3 +176,5 @@ opt-level = 3
opt-level = 3
[profile.release.package.zstd-sys]
opt-level = 3
[profile.release.package.base64-simd]
opt-level = 3
2 changes: 1 addition & 1 deletion ext/web/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ path = "lib.rs"

[dependencies]
async-trait = "0.1.51"
base64 = "0.13.0"
base64-simd = "0.6.2"
deno_core = { version = "0.140.0", path = "../../core" }
encoding_rs = "0.8.31"
flate2 = "1"
Expand Down
69 changes: 18 additions & 51 deletions ext/web/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,74 +124,41 @@ pub fn init<P: TimersPermission + 'static>(

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

#[op]
fn op_base64_atob(mut s: ByteString) -> Result<ByteString, AnyError> {
s.retain(|c| !c.is_ascii_whitespace());

// If padding is expected, fail if not 4-byte aligned
if s.len() % 4 != 0 && (s.ends_with(b"==") || s.ends_with(b"=")) {
return Err(
DomExceptionInvalidCharacterError::new("Failed to decode base64.").into(),
);
}

Ok(b64_decode(&s)?.into())
fn op_base64_atob(s: ByteString) -> Result<ByteString, AnyError> {
Ok(forgiving_base64_decode(s.into())?.into())
}

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

// "If the length of input divides by 4 leaving a remainder of 1,
// throw an InvalidCharacterError exception and abort these steps."
if input.len() % 4 == 1 {
return Err(
DomExceptionInvalidCharacterError::new("Failed to decode base64.").into(),
);
}

let cfg = base64::Config::new(base64::CharacterSet::Standard, true)
.decode_allow_trailing_bits(true);
let out = base64::decode_config(input, cfg).map_err(|err| match err {
base64::DecodeError::InvalidByte(_, _) => {
DomExceptionInvalidCharacterError::new(
"Failed to decode base64: invalid character",
)
}
_ => DomExceptionInvalidCharacterError::new(&format!(
"Failed to decode base64: {:?}",
err
)),
})?;
let decoded = base64_simd::Base64::forgiving_decode_inplace(&mut input)
.map_err(|_| error())?;

Ok(out)
let decoded_len = decoded.len();
input.truncate(decoded_len);
Ok(input)
}

#[op]
fn op_base64_encode(s: ZeroCopyBuf) -> String {
b64_encode(&s)
forgiving_base64_encode(s.as_ref())
}

#[op]
fn op_base64_btoa(s: ByteString) -> String {
b64_encode(s)
forgiving_base64_encode(s.as_ref())
}

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

#[derive(Deserialize)]
Expand Down

0 comments on commit 1328a56

Please sign in to comment.