From 4dcb427bec5110b72d7e7da95c8932c72ba37193 Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Tue, 18 Mar 2025 23:30:43 +0900 Subject: [PATCH] std_detect: Remove /proc/cpuinfo-based detection --- crates/std_detect/README.md | 4 +- .../std_detect/src/detect/os/linux/aarch64.rs | 119 +------ crates/std_detect/src/detect/os/linux/arm.rs | 39 +-- .../std_detect/src/detect/os/linux/auxvec.rs | 4 +- .../std_detect/src/detect/os/linux/cpuinfo.rs | 331 ------------------ crates/std_detect/src/detect/os/linux/mips.rs | 4 +- crates/std_detect/src/detect/os/linux/mod.rs | 3 - .../std_detect/src/detect/os/linux/powerpc.rs | 11 +- 8 files changed, 7 insertions(+), 508 deletions(-) delete mode 100644 crates/std_detect/src/detect/os/linux/cpuinfo.rs diff --git a/crates/std_detect/README.md b/crates/std_detect/README.md index 0053d777aa..009c10bebe 100644 --- a/crates/std_detect/README.md +++ b/crates/std_detect/README.md @@ -41,7 +41,7 @@ is undefined. have the minimum supported API level higher than [Android 4.3 (API level 18) that added `getauxval`](https://github.com/aosp-mirror/platform_bionic/blob/d3ebc2f7c49a9893b114124d4a6b315f3a328764/libc/include/sys/auxv.h#L49). * `std_detect_file_io` (enabled by default, requires `std`): Enable to perform run-time feature -detection using file APIs (e.g. `/proc/cpuinfo`, etc.) if other more performant +detection using file APIs (e.g. `/proc/self/auxv`, etc.) if other more performant methods fail. This feature requires `libstd` as a dependency, preventing the crate from working on applications in which `std` is not available. @@ -58,7 +58,7 @@ crate from working on applications in which `std` is not available. * Linux/Android: * `arm{32, 64}`, `mips{32,64}{,el}`, `powerpc{32,64}{,le}`, `loongarch64`, `s390x`: `std_detect` supports these on Linux by querying ELF auxiliary vectors (using `getauxval` - when available), and if that fails, by querying `/proc/cpuinfo`. + when available), and if that fails, by querying `/proc/self/auxv`. * `arm64`: partial support for doing run-time feature detection by directly querying `mrs` is implemented for Linux >= 4.11, but not enabled by default. * `riscv{32,64}`: diff --git a/crates/std_detect/src/detect/os/linux/aarch64.rs b/crates/std_detect/src/detect/os/linux/aarch64.rs index d13aa0168c..9caa785564 100644 --- a/crates/std_detect/src/detect/os/linux/aarch64.rs +++ b/crates/std_detect/src/detect/os/linux/aarch64.rs @@ -3,8 +3,7 @@ use super::auxvec; use crate::detect::{Feature, bit, cache}; -/// Try to read the features from the auxiliary vector, and if that fails, try -/// to read them from /proc/cpuinfo. +/// Try to read the features from the auxiliary vector. pub(crate) fn detect_features() -> cache::Initializer { #[cfg(target_os = "android")] let is_exynos9810 = { @@ -27,11 +26,6 @@ pub(crate) fn detect_features() -> cache::Initializer { let hwcap: AtHwcap = auxv.into(); return hwcap.cache(is_exynos9810); } - #[cfg(feature = "std_detect_file_io")] - if let Ok(c) = super::cpuinfo::CpuInfo::new() { - let hwcap: AtHwcap = c.into(); - return hwcap.cache(is_exynos9810); - } cache::Initializer::default() } @@ -249,117 +243,6 @@ impl From for AtHwcap { } } -#[cfg(feature = "std_detect_file_io")] -impl From for AtHwcap { - /// Reads AtHwcap from /proc/cpuinfo . - fn from(c: super::cpuinfo::CpuInfo) -> Self { - let f = &c.field("Features"); - AtHwcap { - // 64-bit names. FIXME: In 32-bit compatibility mode /proc/cpuinfo will - // map some of the 64-bit names to some 32-bit feature names. This does not - // cover that yet. - fp: f.has("fp"), - asimd: f.has("asimd"), - // evtstrm: f.has("evtstrm"), - aes: f.has("aes"), - pmull: f.has("pmull"), - sha1: f.has("sha1"), - sha2: f.has("sha2"), - crc32: f.has("crc32"), - atomics: f.has("atomics"), - fphp: f.has("fphp"), - asimdhp: f.has("asimdhp"), - // cpuid: f.has("cpuid"), - asimdrdm: f.has("asimdrdm"), - jscvt: f.has("jscvt"), - fcma: f.has("fcma"), - lrcpc: f.has("lrcpc"), - dcpop: f.has("dcpop"), - sha3: f.has("sha3"), - sm3: f.has("sm3"), - sm4: f.has("sm4"), - asimddp: f.has("asimddp"), - sha512: f.has("sha512"), - sve: f.has("sve"), - fhm: f.has("asimdfhm"), - dit: f.has("dit"), - uscat: f.has("uscat"), - ilrcpc: f.has("ilrcpc"), - flagm: f.has("flagm"), - ssbs: f.has("ssbs"), - sb: f.has("sb"), - paca: f.has("paca"), - pacg: f.has("pacg"), - - // AT_HWCAP2 - dcpodp: f.has("dcpodp"), - sve2: f.has("sve2"), - sveaes: f.has("sveaes"), - svepmull: f.has("svepmull"), - svebitperm: f.has("svebitperm"), - svesha3: f.has("svesha3"), - svesm4: f.has("svesm4"), - flagm2: f.has("flagm2"), - frint: f.has("frint"), - // svei8mm: f.has("svei8mm"), - svef32mm: f.has("svef32mm"), - svef64mm: f.has("svef64mm"), - // svebf16: f.has("svebf16"), - i8mm: f.has("i8mm"), - bf16: f.has("bf16"), - // dgh: f.has("dgh"), - rng: f.has("rng"), - bti: f.has("bti"), - mte: f.has("mte"), - ecv: f.has("ecv"), - // afp: f.has("afp"), - // rpres: f.has("rpres"), - // mte3: f.has("mte3"), - sme: f.has("sme"), - smei16i64: f.has("smei16i64"), - smef64f64: f.has("smef64f64"), - // smei8i32: f.has("smei8i32"), - // smef16f32: f.has("smef16f32"), - // smeb16f32: f.has("smeb16f32"), - // smef32f32: f.has("smef32f32"), - smefa64: f.has("smefa64"), - wfxt: f.has("wfxt"), - // ebf16: f.has("ebf16"), - // sveebf16: f.has("sveebf16"), - cssc: f.has("cssc"), - // rprfm: f.has("rprfm"), - sve2p1: f.has("sve2p1"), - sme2: f.has("sme2"), - sme2p1: f.has("sme2p1"), - // smei16i32: f.has("smei16i32"), - // smebi32i32: f.has("smebi32i32"), - smeb16b16: f.has("smeb16b16"), - smef16f16: f.has("smef16f16"), - mops: f.has("mops"), - hbc: f.has("hbc"), - sveb16b16: f.has("sveb16b16"), - lrcpc3: f.has("lrcpc3"), - lse128: f.has("lse128"), - fpmr: f.has("fpmr"), - lut: f.has("lut"), - faminmax: f.has("faminmax"), - f8cvt: f.has("f8cvt"), - f8fma: f.has("f8fma"), - f8dp4: f.has("f8dp4"), - f8dp2: f.has("f8dp2"), - f8e4m3: f.has("f8e4m3"), - f8e5m2: f.has("f8e5m2"), - smelutv2: f.has("smelutv2"), - smef8f16: f.has("smef8f16"), - smef8f32: f.has("smef8f32"), - smesf8fma: f.has("smesf8fma"), - smesf8dp4: f.has("smesf8dp4"), - smesf8dp2: f.has("smesf8dp2"), - // pauthlr: f.has("pauthlr"), - } - } -} - impl AtHwcap { /// Initializes the cache from the feature -bits. /// diff --git a/crates/std_detect/src/detect/os/linux/arm.rs b/crates/std_detect/src/detect/os/linux/arm.rs index 7a68d2f87b..bbb173227d 100644 --- a/crates/std_detect/src/detect/os/linux/arm.rs +++ b/crates/std_detect/src/detect/os/linux/arm.rs @@ -3,8 +3,7 @@ use super::auxvec; use crate::detect::{Feature, bit, cache}; -/// Try to read the features from the auxiliary vector, and if that fails, try -/// to read them from /proc/cpuinfo. +/// Try to read the features from the auxiliary vector. pub(crate) fn detect_features() -> cache::Initializer { let mut value = cache::Initializer::default(); let enable_feature = |value: &mut cache::Initializer, f, enable| { @@ -31,41 +30,5 @@ pub(crate) fn detect_features() -> cache::Initializer { ); return value; } - - #[cfg(feature = "std_detect_file_io")] - if let Ok(c) = super::cpuinfo::CpuInfo::new() { - enable_feature( - &mut value, - Feature::neon, - c.field("Features").has("neon") && !has_broken_neon(&c), - ); - enable_feature(&mut value, Feature::i8mm, c.field("Features").has("i8mm")); - enable_feature( - &mut value, - Feature::dotprod, - c.field("Features").has("asimddp"), - ); - enable_feature(&mut value, Feature::pmull, c.field("Features").has("pmull")); - enable_feature(&mut value, Feature::crc, c.field("Features").has("crc32")); - enable_feature(&mut value, Feature::aes, c.field("Features").has("aes")); - enable_feature( - &mut value, - Feature::sha2, - c.field("Features").has("sha1") && c.field("Features").has("sha2"), - ); - return value; - } value } - -/// Is the CPU known to have a broken NEON unit? -/// -/// See https://crbug.com/341598. -#[cfg(feature = "std_detect_file_io")] -fn has_broken_neon(cpuinfo: &super::cpuinfo::CpuInfo) -> bool { - cpuinfo.field("CPU implementer") == "0x51" - && cpuinfo.field("CPU architecture") == "7" - && cpuinfo.field("CPU variant") == "0x1" - && cpuinfo.field("CPU part") == "0x04d" - && cpuinfo.field("CPU revision") == "0" -} diff --git a/crates/std_detect/src/detect/os/linux/auxvec.rs b/crates/std_detect/src/detect/os/linux/auxvec.rs index 0f643bbd27..4293df20da 100644 --- a/crates/std_detect/src/detect/os/linux/auxvec.rs +++ b/crates/std_detect/src/detect/os/linux/auxvec.rs @@ -53,9 +53,7 @@ pub(crate) struct AuxVec { /// - If that fails, this function returns an error. /// /// Note that run-time feature detection is not invoked for features that can -/// be detected at compile-time. Also note that if this function returns an -/// error, cpuinfo still can (and will) be used to try to perform run-time -/// feature detection on some platforms. +/// be detected at compile-time. /// /// Note: The `std_detect_dlsym_getauxval` cargo feature is ignored on /// `*-linux-{gnu,musl,ohos}*` and `*-android*` targets because we can safely assume `getauxval` diff --git a/crates/std_detect/src/detect/os/linux/cpuinfo.rs b/crates/std_detect/src/detect/os/linux/cpuinfo.rs deleted file mode 100644 index 48a5c97286..0000000000 --- a/crates/std_detect/src/detect/os/linux/cpuinfo.rs +++ /dev/null @@ -1,331 +0,0 @@ -//! Parses /proc/cpuinfo -#![cfg_attr(not(target_arch = "arm"), allow(dead_code))] - -use alloc::string::String; - -/// cpuinfo -pub(crate) struct CpuInfo { - raw: String, -} - -impl CpuInfo { - /// Reads /proc/cpuinfo into CpuInfo. - pub(crate) fn new() -> Result { - let raw = super::read_file("/proc/cpuinfo")?; - Ok(Self { - raw: String::from_utf8(raw).map_err(|_| ())?, - }) - } - /// Returns the value of the cpuinfo `field`. - pub(crate) fn field(&self, field: &str) -> CpuInfoField<'_> { - for l in self.raw.lines() { - if l.trim().starts_with(field) { - return CpuInfoField::new(l.split(": ").nth(1)); - } - } - CpuInfoField(None) - } - - /// Returns the `raw` contents of `/proc/cpuinfo` - #[cfg(test)] - fn raw(&self) -> &String { - &self.raw - } - - #[cfg(test)] - fn from_str(other: &str) -> Result { - Ok(Self { - raw: String::from(other), - }) - } -} - -/// Field of cpuinfo -#[derive(Debug)] -pub(crate) struct CpuInfoField<'a>(Option<&'a str>); - -impl<'a> PartialEq<&'a str> for CpuInfoField<'a> { - fn eq(&self, other: &&'a str) -> bool { - match self.0 { - None => other.is_empty(), - Some(f) => f == other.trim(), - } - } -} - -impl<'a> CpuInfoField<'a> { - pub(crate) fn new<'b>(v: Option<&'b str>) -> CpuInfoField<'b> { - match v { - None => CpuInfoField::<'b>(None), - Some(f) => CpuInfoField::<'b>(Some(f.trim())), - } - } - /// Does the field exist? - #[cfg(test)] - pub(crate) fn exists(&self) -> bool { - self.0.is_some() - } - /// Does the field contain `other`? - pub(crate) fn has(&self, other: &str) -> bool { - match self.0 { - None => other.is_empty(), - Some(f) => { - let other = other.trim(); - for v in f.split(' ') { - if v == other { - return true; - } - } - false - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn raw_dump() { - let cpuinfo = CpuInfo::new().unwrap(); - if cpuinfo.field("vendor_id") == "GenuineIntel" { - assert!(cpuinfo.field("flags").exists()); - assert!(!cpuinfo.field("vendor33_id").exists()); - assert!(cpuinfo.field("flags").has("sse")); - assert!(!cpuinfo.field("flags").has("avx314")); - } - println!("{}", cpuinfo.raw()); - } - - const CORE_DUO_T6500: &str = r"processor : 0 -vendor_id : GenuineIntel -cpu family : 6 -model : 23 -model name : Intel(R) Core(TM)2 Duo CPU T6500 @ 2.10GHz -stepping : 10 -microcode : 0xa0b -cpu MHz : 1600.000 -cache size : 2048 KB -physical id : 0 -siblings : 2 -core id : 0 -cpu cores : 2 -apicid : 0 -initial apicid : 0 -fdiv_bug : no -hlt_bug : no -f00f_bug : no -coma_bug : no -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe nx lm constant_tsc arch_perfmon pebs bts aperfmperf pni dtes64 monitor ds_cpl est tm2 ssse3 cx16 xtpr pdcm sse4_1 xsave lahf_lm dtherm -bogomips : 4190.43 -clflush size : 64 -cache_alignment : 64 -address sizes : 36 bits physical, 48 bits virtual -power management: -"; - - #[test] - fn core_duo_t6500() { - let cpuinfo = CpuInfo::from_str(CORE_DUO_T6500).unwrap(); - assert_eq!(cpuinfo.field("vendor_id"), "GenuineIntel"); - assert_eq!(cpuinfo.field("cpu family"), "6"); - assert_eq!(cpuinfo.field("model"), "23"); - assert_eq!( - cpuinfo.field("model name"), - "Intel(R) Core(TM)2 Duo CPU T6500 @ 2.10GHz" - ); - assert_eq!( - cpuinfo.field("flags"), - "fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe nx lm constant_tsc arch_perfmon pebs bts aperfmperf pni dtes64 monitor ds_cpl est tm2 ssse3 cx16 xtpr pdcm sse4_1 xsave lahf_lm dtherm" - ); - assert!(cpuinfo.field("flags").has("fpu")); - assert!(cpuinfo.field("flags").has("dtherm")); - assert!(cpuinfo.field("flags").has("sse2")); - assert!(!cpuinfo.field("flags").has("avx")); - } - - const ARM_CORTEX_A53: &str = r"Processor : AArch64 Processor rev 3 (aarch64) - processor : 0 - processor : 1 - processor : 2 - processor : 3 - processor : 4 - processor : 5 - processor : 6 - processor : 7 - Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 - CPU implementer : 0x41 - CPU architecture: AArch64 - CPU variant : 0x0 - CPU part : 0xd03 - CPU revision : 3 - - Hardware : HiKey Development Board - "; - - #[test] - fn arm_cortex_a53() { - let cpuinfo = CpuInfo::from_str(ARM_CORTEX_A53).unwrap(); - assert_eq!( - cpuinfo.field("Processor"), - "AArch64 Processor rev 3 (aarch64)" - ); - assert_eq!( - cpuinfo.field("Features"), - "fp asimd evtstrm aes pmull sha1 sha2 crc32" - ); - assert!(cpuinfo.field("Features").has("pmull")); - assert!(!cpuinfo.field("Features").has("neon")); - assert!(cpuinfo.field("Features").has("asimd")); - } - - const ARM_CORTEX_A57: &str = r"Processor : Cortex A57 Processor rev 1 (aarch64) -processor : 0 -processor : 1 -processor : 2 -processor : 3 -Features : fp asimd aes pmull sha1 sha2 crc32 wp half thumb fastmult vfp edsp neon vfpv3 tlsi vfpv4 idiva idivt -CPU implementer : 0x41 -CPU architecture: 8 -CPU variant : 0x1 -CPU part : 0xd07 -CPU revision : 1"; - - #[test] - fn arm_cortex_a57() { - let cpuinfo = CpuInfo::from_str(ARM_CORTEX_A57).unwrap(); - assert_eq!( - cpuinfo.field("Processor"), - "Cortex A57 Processor rev 1 (aarch64)" - ); - assert_eq!( - cpuinfo.field("Features"), - "fp asimd aes pmull sha1 sha2 crc32 wp half thumb fastmult vfp edsp neon vfpv3 tlsi vfpv4 idiva idivt" - ); - assert!(cpuinfo.field("Features").has("pmull")); - assert!(cpuinfo.field("Features").has("neon")); - assert!(cpuinfo.field("Features").has("asimd")); - } - - const RISCV_RV64GC: &str = r"processor : 0 -hart : 3 -isa : rv64imafdc -mmu : sv39 -uarch : sifive,u74-mc - -processor : 1 -hart : 1 -isa : rv64imafdc -mmu : sv39 -uarch : sifive,u74-mc - -processor : 2 -hart : 2 -isa : rv64imafdc -mmu : sv39 -uarch : sifive,u74-mc - -processor : 3 -hart : 4 -isa : rv64imafdc -mmu : sv39 -uarch : sifive,u74-mc"; - - #[test] - fn riscv_rv64gc() { - let cpuinfo = CpuInfo::from_str(RISCV_RV64GC).unwrap(); - assert_eq!(cpuinfo.field("isa"), "rv64imafdc"); - assert_eq!(cpuinfo.field("mmu"), "sv39"); - assert_eq!(cpuinfo.field("uarch"), "sifive,u74-mc"); - } - - const POWER8E_POWERKVM: &str = r"processor : 0 -cpu : POWER8E (raw), altivec supported -clock : 3425.000000MHz -revision : 2.1 (pvr 004b 0201) - -processor : 1 -cpu : POWER8E (raw), altivec supported -clock : 3425.000000MHz -revision : 2.1 (pvr 004b 0201) - -processor : 2 -cpu : POWER8E (raw), altivec supported -clock : 3425.000000MHz -revision : 2.1 (pvr 004b 0201) - -processor : 3 -cpu : POWER8E (raw), altivec supported -clock : 3425.000000MHz -revision : 2.1 (pvr 004b 0201) - -timebase : 512000000 -platform : pSeries -model : IBM pSeries (emulated by qemu) -machine : CHRP IBM pSeries (emulated by qemu)"; - - #[test] - fn power8_powerkvm() { - let cpuinfo = CpuInfo::from_str(POWER8E_POWERKVM).unwrap(); - assert_eq!(cpuinfo.field("cpu"), "POWER8E (raw), altivec supported"); - - assert!(cpuinfo.field("cpu").has("altivec")); - } - - const POWER5P: &str = r"processor : 0 -cpu : POWER5+ (gs) -clock : 1900.098000MHz -revision : 2.1 (pvr 003b 0201) - -processor : 1 -cpu : POWER5+ (gs) -clock : 1900.098000MHz -revision : 2.1 (pvr 003b 0201) - -processor : 2 -cpu : POWER5+ (gs) -clock : 1900.098000MHz -revision : 2.1 (pvr 003b 0201) - -processor : 3 -cpu : POWER5+ (gs) -clock : 1900.098000MHz -revision : 2.1 (pvr 003b 0201) - -processor : 4 -cpu : POWER5+ (gs) -clock : 1900.098000MHz -revision : 2.1 (pvr 003b 0201) - -processor : 5 -cpu : POWER5+ (gs) -clock : 1900.098000MHz -revision : 2.1 (pvr 003b 0201) - -processor : 6 -cpu : POWER5+ (gs) -clock : 1900.098000MHz -revision : 2.1 (pvr 003b 0201) - -processor : 7 -cpu : POWER5+ (gs) -clock : 1900.098000MHz -revision : 2.1 (pvr 003b 0201) - -timebase : 237331000 -platform : pSeries -machine : CHRP IBM,9133-55A"; - - #[test] - fn power5p() { - let cpuinfo = CpuInfo::from_str(POWER5P).unwrap(); - assert_eq!(cpuinfo.field("cpu"), "POWER5+ (gs)"); - - assert!(!cpuinfo.field("cpu").has("altivec")); - } -} diff --git a/crates/std_detect/src/detect/os/linux/mips.rs b/crates/std_detect/src/detect/os/linux/mips.rs index 159b053ba6..0cfa886988 100644 --- a/crates/std_detect/src/detect/os/linux/mips.rs +++ b/crates/std_detect/src/detect/os/linux/mips.rs @@ -3,8 +3,7 @@ use super::auxvec; use crate::detect::{Feature, bit, cache}; -/// Try to read the features from the auxiliary vector, and if that fails, try -/// to read them from `/proc/cpuinfo`. +/// Try to read the features from the auxiliary vector. pub(crate) fn detect_features() -> cache::Initializer { let mut value = cache::Initializer::default(); let enable_feature = |value: &mut cache::Initializer, f, enable| { @@ -20,6 +19,5 @@ pub(crate) fn detect_features() -> cache::Initializer { enable_feature(&mut value, Feature::msa, bit::test(auxv.hwcap, 1)); return value; } - // TODO: fall back via `cpuinfo`. value } diff --git a/crates/std_detect/src/detect/os/linux/mod.rs b/crates/std_detect/src/detect/os/linux/mod.rs index fbe6bea93c..8c689d0b1f 100644 --- a/crates/std_detect/src/detect/os/linux/mod.rs +++ b/crates/std_detect/src/detect/os/linux/mod.rs @@ -5,9 +5,6 @@ use alloc::vec::Vec; mod auxvec; -#[cfg(feature = "std_detect_file_io")] -mod cpuinfo; - #[cfg(feature = "std_detect_file_io")] fn read_file(path: &str) -> Result, ()> { let mut path = Vec::from(path.as_bytes()); diff --git a/crates/std_detect/src/detect/os/linux/powerpc.rs b/crates/std_detect/src/detect/os/linux/powerpc.rs index 5ebae195f2..39eac8461d 100644 --- a/crates/std_detect/src/detect/os/linux/powerpc.rs +++ b/crates/std_detect/src/detect/os/linux/powerpc.rs @@ -3,8 +3,7 @@ use super::auxvec; use crate::detect::{Feature, cache}; -/// Try to read the features from the auxiliary vector, and if that fails, try -/// to read them from /proc/cpuinfo. +/// Try to read the features from the auxiliary vector. pub(crate) fn detect_features() -> cache::Initializer { let mut value = cache::Initializer::default(); let enable_feature = |value: &mut cache::Initializer, f, enable| { @@ -24,13 +23,5 @@ pub(crate) fn detect_features() -> cache::Initializer { enable_feature(&mut value, Feature::power8, auxv.hwcap2 & 0x80000000 != 0); return value; } - - // PowerPC's /proc/cpuinfo lacks a proper Feature field, - // but `altivec` support is indicated in the `cpu` field. - #[cfg(feature = "std_detect_file_io")] - if let Ok(c) = super::cpuinfo::CpuInfo::new() { - enable_feature(&mut value, Feature::altivec, c.field("cpu").has("altivec")); - return value; - } value }