// 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.
// The 128-bit vector operations used by the SIMD byte and UTF-16 scanners in
// `bytes_find.mbt`, `string_find_code_unit.mbt`, and `string_methods.mbt`.
// `builtin` cannot import `moonbitlang/core/v128` (that would be a cycle), so
// the few ops the scanners need are hosted here directly.
//
// Scanner-only operations are gated to the linear-memory backends that use
// them. `v128_make` and `v128_load` also support aligned bitstring extraction,
// so they remain available on every backend with portable fallback bodies.
// Scanner intrinsic declarations also carry scalar bodies for native builds
// without hardware SIMD support.
///|
fn v128_make(lo : UInt64, hi : UInt64) -> V128 = "%v128.make"
///|
#cfg(any(target="native", target="wasm"))
fn v128_lo(value : V128) -> UInt64 = "%v128.lo"
///|
#cfg(any(target="native", target="wasm"))
fn v128_hi(value : V128) -> UInt64 = "%v128.hi"
///|
#cfg(any(target="native", target="wasm"))
#intrinsic("%v128.i8x16_splat")
fn i8x16_splat(value : Byte) -> V128 {
let lane = value.to_uint64() * (0x0101010101010101 : UInt64)
v128_make(lane, lane)
}
///|
#cfg(any(target="native", target="wasm"))
#intrinsic("%v128.i16x8_splat")
fn i16x8_splat(value : UInt16) -> V128 {
let lane = value.to_uint64() * (0x0001000100010001 : UInt64)
v128_make(lane, lane)
}
///|
#borrow(bytes)
#intrinsic("%v128.v128_load")
fn v128_load(bytes : FixedArray[Byte], offset : Int) -> V128 {
v128_make(
fixedarray_read_uint64_le(bytes, offset),
fixedarray_read_uint64_le(bytes, offset + 8),
)
}
///|
// Loads eight consecutive UTF-16 code units beginning at `offset`. The caller
// must ensure `offset + 8 <= str.length()`.
#borrow(str)
#cfg(any(target="native", target="wasm"))
#intrinsic("%v128.v128_load_i16x8")
fn v128_load_i16x8(str : String, offset : Int) -> V128 {
let lo = for i in 0..<4; word = (0 : UInt64) {
continue word | (str.unsafe_get(offset + i).to_uint64() << (16 * i))
} nobreak {
word
}
let hi = for i in 0..<4; word = (0 : UInt64) {
continue word | (str.unsafe_get(offset + 4 + i).to_uint64() << (16 * i))
} nobreak {
word
}
v128_make(lo, hi)
}
///|
#cfg(any(target="native", target="wasm"))
#intrinsic("%v128.v128_and")
fn v128_and(a : V128, b : V128) -> V128 {
v128_make(v128_lo(a) & v128_lo(b), v128_hi(a) & v128_hi(b))
}
///|
#cfg(any(target="native", target="wasm"))
#intrinsic("%v128.i8x16_eq")
fn i8x16_eq(a : V128, b : V128) -> V128 {
v128_make(
u64_byte_eq_mask(v128_lo(a), v128_lo(b)),
u64_byte_eq_mask(v128_hi(a), v128_hi(b)),
)
}
///|
#cfg(any(target="native", target="wasm"))
#intrinsic("%v128.i16x8_eq")
fn i16x8_eq(a : V128, b : V128) -> V128 {
v128_make(
u64_u16_eq_mask(v128_lo(a), v128_lo(b)),
u64_u16_eq_mask(v128_hi(a), v128_hi(b)),
)
}
///|
#cfg(any(target="native", target="wasm"))
#intrinsic("%v128.i8x16_bitmask")
fn i8x16_bitmask(value : V128) -> Int {
u64_high_bits(v128_lo(value)) | (u64_high_bits(v128_hi(value)) << 8)
}
///|
#cfg(any(target="native", target="wasm"))
#intrinsic("%v128.i16x8_bitmask")
fn i16x8_bitmask(value : V128) -> Int {
u64_u16_high_bits(v128_lo(value)) | (u64_u16_high_bits(v128_hi(value)) << 4)
}
///|
#cfg(any(target="native", target="wasm"))
#intrinsic("%v128.v128_any_true")
fn v128_any_true(value : V128) -> Bool {
v128_lo(value) != 0 || v128_hi(value) != 0
}
///|
// Per-byte equality mask over the eight bytes of a `UInt64`: each byte becomes
// `0xFF` when the operands' bytes match, `0x00` otherwise.
#cfg(any(target="native", target="wasm"))
fn u64_byte_eq_mask(a : UInt64, b : UInt64) -> UInt64 {
for i in 0..<8; result = (0 : UInt64) {
let shift = 8 * i
let mask = if ((a >> shift) & 0xFF) == ((b >> shift) & 0xFF) {
(0xFF : UInt64)
} else {
0
}
continue result | (mask << shift)
} nobreak {
result
}
}
///|
// Per-lane equality mask over the four 16-bit lanes of a `UInt64`: each lane
// becomes `0xFFFF` when the operands' lanes match, `0x0000` otherwise.
#cfg(any(target="native", target="wasm"))
fn u64_u16_eq_mask(a : UInt64, b : UInt64) -> UInt64 {
for i in 0..<4; result = (0 : UInt64) {
let shift = 16 * i
let mask = if ((a >> shift) & 0xFFFF) == ((b >> shift) & 0xFFFF) {
(0xFFFF : UInt64)
} else {
0
}
continue result | (mask << shift)
} nobreak {
result
}
}
///|
// Gathers the high bit (bit 7) of each of the eight bytes of a `UInt64` into
// the low eight bits of an `Int`.
#cfg(any(target="native", target="wasm"))
fn u64_high_bits(value : UInt64) -> Int {
for i in 0..<8; result = 0 {
let bit = ((value >> (8 * i + 7)) & 1).to_int()
continue result | (bit << i)
} nobreak {
result
}
}
///|
// Gathers the high bit (bit 15) of each of the four 16-bit lanes of a `UInt64`
// into the low four bits of an `Int`.
#cfg(any(target="native", target="wasm"))
fn u64_u16_high_bits(value : UInt64) -> Int {
for i in 0..<4; result = 0 {
let bit = ((value >> (16 * i + 15)) & 1).to_int()
continue result | (bit << i)
} nobreak {
result
}
}