// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Vectorized Base64 encoding for the linear-memory backends, after
// Wojciech Muła's SSE implementation
// (http://0x80.pl/notesen/2016-01-12-sse-base64-encoding.html).
//
// Each iteration loads sixteen source bytes, consumes twelve of them, and
// emits sixteen Base64 characters. Because a MoonBit `String` is UTF-16,
// those sixteen characters are widened to thirty-two bytes before being
// stored.
///|
/// Per-class offsets added to a sextet to reach its Base64 character, indexed
/// by the class computed in `encode_block_v128`:
/// 0 -> lowercase, 1..=10 -> digits, 11 -> `+`, 12 -> `/`, 13 -> uppercase.
/// Values are stored modulo 256 because `i8x16_add` wraps.
#cfg(any(target="native", target="wasm"))
fn encode_offsets_v128() -> V128 {
@v128.i8x16_const(
71, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 237, 240, 65, 0, 0,
)
}
///|
/// Turns twelve source bytes (the low twelve lanes of `data`) into sixteen
/// Base64 characters, one per lane.
#cfg(any(target="native", target="wasm"))
fn encode_block_v128(data : V128) -> V128 {
// Spread each three-byte group across one 32-bit lane so that the lane,
// read as a little-endian integer, holds `b0 << 16 | b1 << 8 | b2`. Lane
// byte three is taken from the zero vector.
let zero = @v128.i8x16_splat(0)
let groups = @v128.i8x16_shuffle(
data, zero, 2, 1, 0, 16, 5, 4, 3, 16, 8, 7, 6, 16, 11, 10, 9, 16,
)
// Slice each group into four sextets, positioned so that lane byte `k`
// holds the `k`-th output character. `i16x8`-style multiply tricks are not
// available on SIMD128, but uniform shifts are, and they are cheap.
let sextets = @v128.v128_or_(
@v128.v128_or_(
@v128.v128_and_(
@v128.i32x4_shr_u(groups, 18),
@v128.i32x4_splat(0x0000003F),
),
@v128.v128_and_(
@v128.i32x4_shr_u(groups, 4),
@v128.i32x4_splat(0x00003F00),
),
),
@v128.v128_or_(
@v128.v128_and_(
@v128.i32x4_shl(groups, 10),
@v128.i32x4_splat(0x003F0000),
),
@v128.v128_and_(
@v128.i32x4_shl(groups, 24),
@v128.i32x4_splat(0x3F000000),
),
),
)
// Classify each sextet, then look its offset up in a register-resident
// table. Saturating subtraction collapses 0..=51 to class zero and maps
// 52..=63 to 1..=12; the separate `< 26` test splits uppercase off as
// class thirteen.
let class = @v128.v128_or_(
@v128.i8x16_sub_sat_u(sextets, @v128.i8x16_splat(51)),
@v128.v128_and_(
@v128.i8x16_lt_u(sextets, @v128.i8x16_splat(26)),
@v128.i8x16_splat(13),
),
)
@v128.i8x16_add(sextets, @v128.i8x16_swizzle(encode_offsets_v128(), class))
}
///|
/// Writes one ASCII character as a little-endian UTF-16 code unit.
#cfg(any(target="native", target="wasm"))
#inline
fn write_code_unit(out : FixedArray[Byte], offset : Int32, code : Byte) -> Unit {
out[offset] = code
out[offset + 1] = 0
}
///|
#cfg(any(target="native", target="wasm"))
fn encode_v128(bytes : BytesView, padding : Bool) -> String {
let length = bytes.length()
// Three source bytes become four code units and so eight buffer bytes.
// The vector stores below are not bounds checked, so an input whose
// buffer size would wrap has to be refused before the allocation; the
// scalar encoder builds the string without ever materializing that
// buffer. The bound is a round number well inside the true ceiling,
// since no caller is near it either way.
guard length <= 0x2000_0000 else { return encode_scalar(bytes, padding~) }
let full_groups = length / 3
let remainder = length % 3
let mut char_count = full_groups * 4
if remainder != 0 {
char_count += if padding { 4 } else { remainder + 1 }
}
let out = FixedArray::make(char_count * 2, b'\x00')
// The load reads sixteen bytes but only twelve are consumed, so the loop
// stops four bytes short of the end and the tail is encoded scalar.
for rest = bytes, written = 0 {
match rest {
[v128le(block), ..] as current => {
let chars = encode_block_v128(block)
@v128.v128_store(out, written, @v128.i16x8_extend_low_i8x16_u(chars))
@v128.v128_store(
out,
written + 16,
@v128.i16x8_extend_high_i8x16_u(chars),
)
continue current[12:], written + 32
}
[b0, b1, b2, .. tail] => {
let n = (b0.to_int() << 16) | (b1.to_int() << 8) | b2.to_int()
write_code_unit(out, written, BASE64_STD[(n >> 18) & 0x3F])
write_code_unit(out, written + 2, BASE64_STD[(n >> 12) & 0x3F])
write_code_unit(out, written + 4, BASE64_STD[(n >> 6) & 0x3F])
write_code_unit(out, written + 6, BASE64_STD[n & 0x3F])
continue tail, written + 8
}
[b0, b1] => {
let n = (b0.to_int() << 16) | (b1.to_int() << 8)
write_code_unit(out, written, BASE64_STD[(n >> 18) & 0x3F])
write_code_unit(out, written + 2, BASE64_STD[(n >> 12) & 0x3F])
write_code_unit(out, written + 4, BASE64_STD[(n >> 6) & 0x3F])
if padding {
write_code_unit(out, written + 6, b'=')
}
break
}
[b0] => {
let n = b0.to_int() << 16
write_code_unit(out, written, BASE64_STD[(n >> 18) & 0x3F])
write_code_unit(out, written + 2, BASE64_STD[(n >> 12) & 0x3F])
if padding {
write_code_unit(out, written + 4, b'=')
write_code_unit(out, written + 6, b'=')
}
break
}
[] => break
}
}
out.unsafe_reinterpret_as_bytes().to_unchecked_string()
}