#![allow(dead_code)]
use crate::include::common::bitdepth::BitDepth;
use crate::include::dav1d::picture::PicOffset;
use crate::src::strided::Strided as _;
use std::ops::Deref;
use std::ops::DerefMut;
#[repr(C, align(64))]
#[derive(Clone, Copy, zerocopy::FromBytes, zerocopy::IntoBytes, zerocopy::Immutable)]
pub(crate) struct Chunk([u8; 64]);
const CHUNK: usize = 64;
pub(crate) struct ReconBand {
planes: [Vec<Chunk>; 3],
stride: [usize; 3],
rows: [usize; 3],
origin: [(usize, usize); 3],
live: [(usize, usize); 3],
n_planes: usize,
armed: bool,
}
impl Default for ReconBand {
fn default() -> Self {
Self {
planes: [const { Vec::new() }; 3],
stride: [0; 3],
rows: [0; 3],
origin: [(0, 0); 3],
live: [(0, 0); 3],
n_planes: 0,
armed: false,
}
}
}
impl ReconBand {
#[inline(always)]
pub(crate) fn armed(&self) -> bool {
self.armed
}
pub(crate) fn disarm(&mut self) {
self.armed = false;
}
pub(crate) fn arm(
&mut self,
n_planes: usize,
geom: &[(usize, usize, usize, usize, usize, usize); 3],
) {
self.armed = false;
self.n_planes = n_planes;
for pl in 0..n_planes {
let (row0, col0, rows, cols, sb_cols, pixel_size) = geom[pl];
let alloc_cols = if sb_cols == 0 {
cols
} else {
cols.next_multiple_of(sb_cols)
};
let want_stride = (alloc_cols * pixel_size).next_multiple_of(CHUNK);
let want_chunks = rows * (want_stride / CHUNK);
if self.planes[pl].len() < want_chunks {
let extra = want_chunks - self.planes[pl].len();
if self.planes[pl].try_reserve(extra).is_err() {
return;
}
self.planes[pl].resize(want_chunks, Chunk([0; CHUNK]));
}
self.stride[pl] = want_stride;
self.rows[pl] = rows;
self.origin[pl] = (row0, col0);
self.live[pl] = (rows, cols);
}
self.armed = true;
}
pub(crate) fn set_live_rows(&mut self, live_rows: [usize; 3]) {
for pl in 0..self.n_planes {
self.live[pl].0 = live_rows[pl].min(self.rows[pl]);
}
}
#[inline(always)]
pub(crate) fn n_planes(&self) -> usize {
self.n_planes
}
#[inline(always)]
pub(crate) fn plane_geometry(&self, pl: usize) -> (usize, usize, usize, usize) {
let (row0, col0) = self.origin[pl];
let (rows, cols) = self.live[pl];
(row0, col0, rows, cols)
}
#[inline(always)]
pub(crate) fn row_bytes(&self, pl: usize, row: usize, len: usize) -> &[u8] {
let stride = self.stride[pl];
let all: &[u8] = zerocopy::IntoBytes::as_bytes(&self.planes[pl][..]);
&all[row * stride..][..len]
}
#[inline(always)]
pub(crate) fn at<BD: BitDepth>(&mut self, pl: usize, row: usize, col: usize) -> Band<'_> {
let (row0, col0) = self.origin[pl];
let stride = self.stride[pl];
let pixel_size = core::mem::size_of::<BD::Pixel>();
let brow = row.checked_sub(row0).expect("recon band row out of range");
let bcol = col.checked_sub(col0).expect("recon band col out of range");
assert!(brow < self.rows[pl], "recon band row out of range");
let bytes: &mut [u8] = zerocopy::IntoBytes::as_mut_bytes(&mut self.planes[pl][..]);
Band {
offset: (brow * stride) / pixel_size + bcol,
bytes,
stride: stride as isize,
}
}
}
pub(crate) struct Band<'a> {
bytes: &'a mut [u8],
offset: usize,
stride: isize,
}
#[derive(Clone, Copy)]
pub(crate) struct BandRef<'a> {
bytes: &'a [u8],
offset: usize,
stride: isize,
}
pub(crate) enum ReconDst<'a> {
Pic(PicOffset<'a>),
Own(Band<'a>),
}
#[derive(Clone, Copy)]
pub(crate) enum ReconSrc<'a> {
Pic(PicOffset<'a>),
Own(BandRef<'a>),
}
pub(crate) enum PxMut<'a, BD: BitDepth> {
Pic(
crate::src::disjoint_mut::DisjointMutGuard<
'a,
crate::include::dav1d::picture::Rav1dPictureDataComponentInner,
[BD::Pixel],
>,
),
Own(&'a mut [BD::Pixel]),
}
impl<BD: BitDepth> Deref for PxMut<'_, BD> {
type Target = [BD::Pixel];
#[inline(always)]
fn deref(&self) -> &[BD::Pixel] {
match self {
Self::Pic(g) => g,
Self::Own(s) => s,
}
}
}
impl<BD: BitDepth> DerefMut for PxMut<'_, BD> {
#[inline(always)]
fn deref_mut(&mut self) -> &mut [BD::Pixel] {
match self {
Self::Pic(g) => g,
Self::Own(s) => s,
}
}
}
pub(crate) enum Px<'a, BD: BitDepth> {
Pic(
crate::src::disjoint_mut::DisjointImmutGuard<
'a,
crate::include::dav1d::picture::Rav1dPictureDataComponentInner,
[BD::Pixel],
>,
),
Own(&'a [BD::Pixel]),
}
impl<BD: BitDepth> Deref for Px<'_, BD> {
type Target = [BD::Pixel];
#[inline(always)]
fn deref(&self) -> &[BD::Pixel] {
match self {
Self::Pic(g) => g,
Self::Own(s) => s,
}
}
}
pub(crate) enum DstBlock<'a, BD: BitDepth> {
Pic(crate::include::dav1d::picture::BlockMut<'a, BD>),
Own {
bytes: &'a mut [u8],
base: usize,
stride: isize,
},
}
impl<BD: BitDepth> DstBlock<'_, BD> {
#[inline(always)]
pub(crate) fn as_mut_bytes(&mut self) -> &mut [u8] {
match self {
Self::Pic(b) => b.as_mut_bytes(),
Self::Own { bytes, .. } => bytes,
}
}
#[inline(always)]
pub(crate) fn base(&self) -> usize {
match self {
Self::Pic(b) => b.base(),
Self::Own { base, .. } => *base,
}
}
#[inline(always)]
pub(crate) fn byte_stride(&self) -> isize {
match self {
Self::Pic(b) => b.byte_stride(),
Self::Own { stride, .. } => *stride,
}
}
}
#[inline]
fn px<BD: BitDepth>(bytes: &[u8], off: usize, len: usize) -> &[BD::Pixel] {
let size = core::mem::size_of::<BD::Pixel>();
let s = &bytes[off * size..][..len * size];
zerocopy::FromBytes::ref_from_bytes(s).expect("band row pixel reinterpretation")
}
#[inline]
fn px_mut<BD: BitDepth>(bytes: &mut [u8], off: usize, len: usize) -> &mut [BD::Pixel] {
let size = core::mem::size_of::<BD::Pixel>();
let s = &mut bytes[off * size..][..len * size];
zerocopy::FromBytes::mut_from_bytes(s).expect("band row pixel reinterpretation")
}
impl<'a> ReconDst<'a> {
#[inline(always)]
pub(crate) fn stride(&self) -> isize {
match self {
Self::Pic(p) => p.stride(),
Self::Own(b) => b.stride,
}
}
#[inline(always)]
pub(crate) fn pixel_stride<BD: BitDepth>(&self) -> isize {
match self {
Self::Pic(p) => p.pixel_stride::<BD>(),
Self::Own(b) => b.stride / core::mem::size_of::<BD::Pixel>() as isize,
}
}
#[inline(always)]
pub(crate) fn at(&mut self, delta: isize) -> ReconDst<'_> {
match self {
Self::Pic(p) => ReconDst::Pic(*p + delta),
Self::Own(b) => ReconDst::Own(Band {
offset: b.offset.wrapping_add_signed(delta),
bytes: b.bytes,
stride: b.stride,
}),
}
}
#[inline(always)]
pub(crate) fn as_src(&self) -> ReconSrc<'_> {
match self {
Self::Pic(p) => ReconSrc::Pic(*p),
Self::Own(b) => ReconSrc::Own(BandRef {
bytes: b.bytes,
offset: b.offset,
stride: b.stride,
}),
}
}
#[inline(always)]
#[cfg_attr(not(feature = "asm"), allow(dead_code))]
pub(crate) fn as_pic(&self) -> Option<PicOffset<'a>> {
match self {
Self::Pic(p) => Some(*p),
Self::Own(_) => None,
}
}
#[inline(always)]
pub(crate) fn slice_mut<BD: BitDepth>(&mut self, len: usize) -> PxMut<'_, BD> {
match self {
Self::Pic(p) => PxMut::Pic(p.slice_mut::<BD>(len)),
Self::Own(b) => PxMut::Own(px_mut::<BD>(b.bytes, b.offset, len)),
}
}
#[cfg(feature = "__simd_test")]
pub(crate) fn copy_out<BD: BitDepth>(&self, w: usize, h: usize) -> Vec<BD::Pixel> {
let ps = self.pixel_stride::<BD>();
let mut out = Vec::with_capacity(w * h);
for y in 0..h {
let row = self.as_src().at(ps * y as isize);
out.extend_from_slice(&row.slice::<BD>(w));
}
out
}
#[cfg(feature = "__simd_test")]
pub(crate) fn copy_in<BD: BitDepth>(&mut self, w: usize, h: usize, src: &[BD::Pixel]) {
assert_eq!(src.len(), w * h, "copy_in expects exactly w*h pixels");
let ps = self.pixel_stride::<BD>();
for y in 0..h {
let mut row = self.at(ps * y as isize);
row.slice_mut::<BD>(w)
.copy_from_slice(&src[y * w..(y + 1) * w]);
}
}
#[inline(always)]
pub(crate) fn slice<BD: BitDepth>(&self, len: usize) -> Px<'_, BD> {
match self {
Self::Pic(p) => Px::Pic(p.slice::<BD>(len)),
Self::Own(b) => Px::Own(px::<BD>(b.bytes, b.offset, len)),
}
}
#[inline(always)]
pub(crate) fn get<BD: BitDepth>(&self) -> BD::Pixel {
match self {
Self::Pic(p) => *p.index::<BD>(),
Self::Own(b) => px::<BD>(b.bytes, b.offset, 1)[0],
}
}
#[inline(always)]
pub(crate) fn set<BD: BitDepth>(&mut self, v: BD::Pixel) {
match self {
Self::Pic(p) => *p.index_mut::<BD>() = v,
Self::Own(b) => px_mut::<BD>(b.bytes, b.offset, 1)[0] = v,
}
}
#[inline(always)]
pub(crate) fn for_rows_mut<BD: BitDepth, F: FnMut(usize, &mut [BD::Pixel])>(
&mut self,
w: usize,
h: usize,
mut f: F,
) {
match self {
Self::Pic(p) => p.for_rows_mut::<BD, F>(w, h, f),
Self::Own(b) => {
if w == 0 || h == 0 {
return;
}
let pxstride = (b.stride / core::mem::size_of::<BD::Pixel>() as isize) as usize;
for row in 0..h {
f(row, px_mut::<BD>(b.bytes, b.offset + row * pxstride, w));
}
}
}
}
#[inline(always)]
pub(crate) fn for_rows<BD: BitDepth, F: FnMut(usize, &[BD::Pixel])>(
&self,
w: usize,
h: usize,
mut f: F,
) {
match self {
Self::Pic(p) => p.for_rows::<BD, F>(w, h, f),
Self::Own(b) => {
if w == 0 || h == 0 {
return;
}
let pxstride = (b.stride / core::mem::size_of::<BD::Pixel>() as isize) as usize;
for row in 0..h {
f(row, px::<BD>(b.bytes, b.offset + row * pxstride, w));
}
}
}
}
#[inline(always)]
pub(crate) fn with_block_mut<BD: BitDepth, R>(
&mut self,
w: usize,
h: usize,
f: impl FnOnce(&mut [u8], usize, isize) -> R,
) -> R {
match self {
Self::Pic(p) => {
crate::include::dav1d::picture::with_pixel_guard_mut::<BD, R>(p, w, h, f)
}
Self::Own(b) => {
let _ = (w, h);
let off = b.offset * core::mem::size_of::<BD::Pixel>();
let stride = b.stride;
f(b.bytes, off, stride)
}
}
}
#[inline(always)]
pub(crate) fn block_mut<BD: BitDepth>(&mut self, w: usize, h: usize) -> DstBlock<'_, BD> {
match self {
Self::Pic(p) => DstBlock::Pic(p.block_mut::<BD>(w, h)),
Self::Own(b) => {
let _ = (w, h);
let off = b.offset * core::mem::size_of::<BD::Pixel>();
DstBlock::Own {
bytes: &mut b.bytes[off..],
base: 0,
stride: b.stride,
}
}
}
}
}
impl<'a> ReconSrc<'a> {
#[inline(always)]
pub(crate) fn stride(&self) -> isize {
match self {
Self::Pic(p) => p.stride(),
Self::Own(b) => b.stride,
}
}
#[inline(always)]
pub(crate) fn pixel_stride<BD: BitDepth>(&self) -> isize {
match self {
Self::Pic(p) => p.pixel_stride::<BD>(),
Self::Own(b) => b.stride / core::mem::size_of::<BD::Pixel>() as isize,
}
}
#[inline(always)]
pub(crate) fn at(&self, delta: isize) -> ReconSrc<'a> {
match self {
Self::Pic(p) => ReconSrc::Pic(*p + delta),
Self::Own(b) => ReconSrc::Own(BandRef {
bytes: b.bytes,
offset: b.offset.wrapping_add_signed(delta),
stride: b.stride,
}),
}
}
#[inline(always)]
#[cfg_attr(not(feature = "asm"), allow(dead_code))]
pub(crate) fn as_pic(&self) -> Option<PicOffset<'a>> {
match self {
Self::Pic(p) => Some(*p),
Self::Own(_) => None,
}
}
#[inline(always)]
pub(crate) fn slice<BD: BitDepth>(&self, len: usize) -> Px<'a, BD> {
match self {
Self::Pic(p) => Px::Pic(p.slice::<BD>(len)),
Self::Own(b) => Px::Own(px::<BD>(b.bytes, b.offset, len)),
}
}
#[inline(always)]
pub(crate) fn get<BD: BitDepth>(&self) -> BD::Pixel {
match self {
Self::Pic(p) => *p.index::<BD>(),
Self::Own(b) => px::<BD>(b.bytes, b.offset, 1)[0],
}
}
#[inline(always)]
pub(crate) fn with_block<BD: BitDepth, R>(
&self,
w: usize,
h: usize,
f: impl FnOnce(&[u8], usize, isize) -> R,
) -> R {
match self {
Self::Pic(p) => {
crate::include::dav1d::picture::with_pixel_guard_immut::<BD, R>(p, w, h, f)
}
Self::Own(b) => {
let _ = (w, h);
f(
b.bytes,
b.offset * core::mem::size_of::<BD::Pixel>(),
b.stride,
)
}
}
}
#[inline(always)]
pub(crate) fn for_rows<BD: BitDepth, F: FnMut(usize, &[BD::Pixel])>(
&self,
w: usize,
h: usize,
mut f: F,
) {
match self {
Self::Pic(p) => p.for_rows::<BD, F>(w, h, f),
Self::Own(b) => {
if w == 0 || h == 0 {
return;
}
let pxstride = (b.stride / core::mem::size_of::<BD::Pixel>() as isize) as usize;
for row in 0..h {
f(row, px::<BD>(b.bytes, b.offset + row * pxstride, w));
}
}
}
}
}
pub(crate) enum ReconPlanes<'a> {
Pic(&'a [crate::include::dav1d::picture::Rav1dPictureDataComponent; 3]),
Own(&'a mut ReconBand),
}
impl<'a> ReconPlanes<'a> {
#[inline(always)]
pub(crate) fn bind(
pic: &'a [crate::include::dav1d::picture::Rav1dPictureDataComponent; 3],
band: &'a mut ReconBand,
) -> Self {
if band.armed {
Self::Own(band)
} else {
Self::Pic(pic)
}
}
#[inline(always)]
pub(crate) fn is_owned(&self) -> bool {
matches!(self, Self::Own(_))
}
#[inline(always)]
pub(crate) fn pixel_stride<BD: BitDepth>(&self, pl: usize) -> isize {
match self {
Self::Pic(p) => p[pl].pixel_stride::<BD>(),
Self::Own(b) => (b.stride[pl] / core::mem::size_of::<BD::Pixel>()) as isize,
}
}
#[inline(always)]
pub(crate) fn dst<BD: BitDepth>(&mut self, pl: usize, row: usize, col: usize) -> ReconDst<'_> {
match self {
Self::Pic(p) => {
let d = &p[pl];
ReconDst::Pic(
d.with_offset::<BD>() + (row as isize * d.pixel_stride::<BD>() + col as isize),
)
}
Self::Own(b) => ReconDst::Own(b.at::<BD>(pl, row, col)),
}
}
#[inline(always)]
pub(crate) fn src<BD: BitDepth>(&self, pl: usize, row: usize, col: usize) -> ReconSrc<'_> {
match self {
Self::Pic(p) => {
let d = &p[pl];
ReconSrc::Pic(
d.with_offset::<BD>() + (row as isize * d.pixel_stride::<BD>() + col as isize),
)
}
Self::Own(b) => {
let (row0, col0) = b.origin[pl];
let stride = b.stride[pl];
let brow = row - row0;
assert!(brow < b.rows[pl], "recon band row out of range");
ReconSrc::Own(BandRef {
bytes: zerocopy::IntoBytes::as_bytes(&b.planes[pl][..]),
offset: (brow * stride) / core::mem::size_of::<BD::Pixel>() + (col - col0),
stride: stride as isize,
})
}
}
}
}
use crate::include::dav1d::headers::Rav1dPixelLayout;
use crate::src::internal::Rav1dContext;
use crate::src::internal::Rav1dFrameData;
use crate::src::internal::Rav1dTaskContext;
#[cfg(feature = "__probe_owned_recon")]
fn enabled() -> bool {
use std::sync::OnceLock;
static ON: OnceLock<bool> = OnceLock::new();
*ON.get_or_init(|| !matches!(std::env::var("RAV1D_OWNED_RECON").as_deref(), Ok("0")))
}
#[cfg(not(feature = "__probe_owned_recon"))]
#[inline(always)]
fn enabled() -> bool {
true
}
#[cfg(all(test, not(feature = "__probe_owned_recon")))]
#[test]
fn ordinary_build_keeps_owned_recon_enabled() {
assert!(enabled());
}
#[cfg(all(test, feature = "__probe_owned_recon"))]
#[test]
#[ignore = "requires RAV1D_OWNED_RECON=0 in a fresh process"]
fn diagnostic_build_can_disable_owned_recon() {
assert!(!enabled());
}
pub(crate) fn frame_setup(c: &Rav1dContext, f: &mut Rav1dFrameData) {
f.owned_recon = false;
if !enabled() {
return;
}
if cfg!(feature = "c-ffi") {
return;
}
if cfg!(feature = "__simd_test") {
return;
}
let Some(frame_hdr) = f.frame_hdr.as_ref() else {
return;
};
let frame_hdr = &***frame_hdr;
if !frame_hdr.frame_type.is_key_or_intra() {
return;
}
if frame_hdr.allow_intrabc {
return;
}
if c.fc.len() > 1 {
return;
}
if f.cur.data.is_none() {
return;
}
let model = &f.cur.data.as_ref().unwrap().data;
let n_planes = if f.cur.p.layout == Rav1dPixelLayout::I400 {
1
} else {
3
};
for pl in 0..n_planes {
if model[pl].stride() <= 0 || model[pl].byte_len() == 0 {
return;
}
}
f.owned_recon = true;
}
fn band_geometry(
f: &Rav1dFrameData,
t: &Rav1dTaskContext,
) -> (usize, [(usize, usize, usize, usize, usize, usize); 3]) {
let ts = &f.ts[t.ts];
let layout = f.cur.p.layout;
let n_planes = if layout == Rav1dPixelLayout::I400 {
1
} else {
3
};
let ss_ver = (layout == Rav1dPixelLayout::I420) as usize;
let ss_hor = (layout != Rav1dPixelLayout::I444) as usize;
let pixel_size = if f.cur.p.bpc > 8 { 2 } else { 1 };
let row0 = (t.b.y * 4) as usize;
let col0 = (ts.tiling.col_start * 4) as usize;
let rows = (f.sb_step * 4) as usize;
let cols = ((ts.tiling.col_end - ts.tiling.col_start) * 4) as usize;
let sb_cols = (f.sb_step * 4) as usize;
let mut geom = [(0, 0, 0, 0, 0, pixel_size); 3];
geom[0] = (row0, col0, rows, cols, sb_cols, pixel_size);
for pl in 1..n_planes {
geom[pl] = (
row0 >> ss_ver,
col0 >> ss_hor,
rows >> ss_ver,
cols >> ss_hor,
sb_cols >> ss_hor,
pixel_size,
);
}
(n_planes, geom)
}
pub(crate) fn arm_sbrow(f: &Rav1dFrameData, t: &mut Rav1dTaskContext) {
t.recon_band.disarm();
if !f.owned_recon {
return;
}
let ts = &f.ts[t.ts];
if ts.tiling.col_end <= ts.tiling.col_start || t.b.y < 0 {
return;
}
let (n_planes, geom) = band_geometry(f, t);
t.recon_band.arm(n_planes, &geom);
if !t.recon_band.armed() {
return;
}
let y1 = std::cmp::min(t.b.y + f.sb_step, ts.tiling.row_end);
if y1 <= t.b.y {
t.recon_band.disarm();
return;
}
let live = ((y1 - t.b.y) * 4) as usize;
let layout = f.cur.p.layout;
let ss_ver = (layout == Rav1dPixelLayout::I420) as usize;
t.recon_band
.set_live_rows([live, live >> ss_ver, live >> ss_ver]);
}
pub(crate) fn stitch_sbrow<BD: BitDepth>(f: &Rav1dFrameData, t: &mut Rav1dTaskContext) {
if !t.recon_band.armed() {
return;
}
let dst_planes = &f.cur.data.as_ref().unwrap().data;
let pixel_size = core::mem::size_of::<BD::Pixel>();
for pl in 0..t.recon_band.n_planes() {
let (row0, col0, rows, cols) = t.recon_band.plane_geometry(pl);
let dst_stride = dst_planes[pl].pixel_stride::<BD>() as usize;
let len = cols * pixel_size;
for row in 0..rows {
let src = t.recon_band.row_bytes(pl, row, len);
let off = (row0 + row) * dst_stride + col0;
let mut dst = dst_planes[pl].slice_mut::<BD, _>((off.., ..cols));
zerocopy::IntoBytes::as_mut_bytes(&mut *dst).copy_from_slice(src);
}
}
t.recon_band.disarm();
}
#[cfg(test)]
mod tests {
use super::*;
use crate::include::common::bitdepth::BitDepth8;
fn band_of(rows: usize, cols: usize) -> ReconBand {
let mut b = ReconBand::default();
b.arm(
1,
&[
(16, 32, rows, cols, 0, 1),
(0, 0, 0, 0, 0, 1),
(0, 0, 0, 0, 0, 1),
],
);
b
}
fn dst_at(b: &mut ReconBand, row: usize, col: usize) -> ReconDst<'_> {
ReconDst::Own(b.at::<BitDepth8>(0, row, col))
}
#[test]
fn translation_is_plane_coordinates_minus_origin() {
let mut b = band_of(8, 96);
dst_at(&mut b, 16, 32).set::<BitDepth8>(7);
dst_at(&mut b, 17, 34).set::<BitDepth8>(9);
let stride = b.stride[0];
assert_eq!(b.row_bytes(0, 0, stride)[0], 7);
assert_eq!(b.row_bytes(0, 1, stride)[2], 9);
}
#[test]
#[should_panic(expected = "recon band row out of range")]
fn a_row_above_the_band_panics_it_does_not_alias() {
let mut b = band_of(8, 96);
dst_at(&mut b, 15, 32).set::<BitDepth8>(1);
}
#[test]
#[should_panic]
fn a_row_below_the_band_panics() {
let mut b = band_of(8, 96);
dst_at(&mut b, 24, 32).set::<BitDepth8>(1);
}
#[test]
fn for_rows_mut_walks_the_bands_own_stride_not_the_pictures() {
let mut b = band_of(4, 96);
assert_eq!(b.stride[0], 128); dst_at(&mut b, 16, 32).for_rows_mut::<BitDepth8, _>(96, 4, |y, row| {
row.fill(y as u8 + 1);
});
for y in 0..4 {
let r = b.row_bytes(0, y, 96);
assert!(r.iter().all(|&v| v == y as u8 + 1), "row {y}");
}
}
}