//! Naming well-known routines in the runtime library. use crate::{ ir::{types, AbiParam, ExternalName, FuncRef, Function, Opcode, Signature, Type}, isa::CallConv, }; use core::fmt; use core::str::FromStr; #[cfg(feature = "enable-serde")] use serde::{Deserialize, Serialize}; /// The name of a runtime library routine. /// /// Runtime library calls are generated for Cranelift IR instructions that don't have an equivalent /// ISA instruction or an easy macro expansion. A `LibCall` is used as a well-known name to refer to /// the runtime library routine. This way, Cranelift doesn't have to know about the naming /// convention in the embedding VM's runtime library. /// /// This list is likely to grow over time. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub enum LibCall { /// probe for stack overflow. These are emitted for functions which need /// when the `enable_probestack` setting is true. Probestack, /// udiv.i64 UdivI64, /// sdiv.i64 SdivI64, /// urem.i64 UremI64, /// srem.i64 SremI64, /// ishl.i64 IshlI64, /// ushr.i64 UshrI64, /// sshr.i64 SshrI64, /// ceil.f32 CeilF32, /// ceil.f64 CeilF64, /// floor.f32 FloorF32, /// floor.f64 FloorF64, /// trunc.f32 TruncF32, /// frunc.f64 TruncF64, /// nearest.f32 NearestF32, /// nearest.f64 NearestF64, /// fma.f32 FmaF32, /// fma.f64 FmaF64, /// libc.memcpy Memcpy, /// libc.memset Memset, /// libc.memmove Memmove, /// libc.memcmp Memcmp, /// Elf __tls_get_addr ElfTlsGetAddr, /// Elf __tls_get_offset ElfTlsGetOffset, // When adding a new variant make sure to add it to `all_libcalls` too. } impl fmt::Display for LibCall { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(self, f) } } impl FromStr for LibCall { type Err = (); fn from_str(s: &str) -> Result { match s { "Probestack" => Ok(Self::Probestack), "UdivI64" => Ok(Self::UdivI64), "SdivI64" => Ok(Self::SdivI64), "UremI64" => Ok(Self::UremI64), "SremI64" => Ok(Self::SremI64), "IshlI64" => Ok(Self::IshlI64), "UshrI64" => Ok(Self::UshrI64), "SshrI64" => Ok(Self::SshrI64), "CeilF32" => Ok(Self::CeilF32), "CeilF64" => Ok(Self::CeilF64), "FloorF32" => Ok(Self::FloorF32), "FloorF64" => Ok(Self::FloorF64), "TruncF32" => Ok(Self::TruncF32), "TruncF64" => Ok(Self::TruncF64), "NearestF32" => Ok(Self::NearestF32), "NearestF64" => Ok(Self::NearestF64), "FmaF32" => Ok(Self::FmaF32), "FmaF64" => Ok(Self::FmaF64), "Memcpy" => Ok(Self::Memcpy), "Memset" => Ok(Self::Memset), "Memmove" => Ok(Self::Memmove), "Memcmp" => Ok(Self::Memcmp), "ElfTlsGetAddr" => Ok(Self::ElfTlsGetAddr), "ElfTlsGetOffset" => Ok(Self::ElfTlsGetOffset), _ => Err(()), } } } impl LibCall { /// Get the well-known library call name to use as a replacement for an instruction with the /// given opcode and controlling type variable. /// /// Returns `None` if no well-known library routine name exists for that instruction. pub fn for_inst(opcode: Opcode, ctrl_type: Type) -> Option { Some(match ctrl_type { types::I64 => match opcode { Opcode::Udiv => Self::UdivI64, Opcode::Sdiv => Self::SdivI64, Opcode::Urem => Self::UremI64, Opcode::Srem => Self::SremI64, Opcode::Ishl => Self::IshlI64, Opcode::Ushr => Self::UshrI64, Opcode::Sshr => Self::SshrI64, _ => return None, }, types::F32 => match opcode { Opcode::Ceil => Self::CeilF32, Opcode::Floor => Self::FloorF32, Opcode::Trunc => Self::TruncF32, Opcode::Nearest => Self::NearestF32, Opcode::Fma => Self::FmaF32, _ => return None, }, types::F64 => match opcode { Opcode::Ceil => Self::CeilF64, Opcode::Floor => Self::FloorF64, Opcode::Trunc => Self::TruncF64, Opcode::Nearest => Self::NearestF64, Opcode::Fma => Self::FmaF64, _ => return None, }, _ => return None, }) } /// Get a list of all known `LibCall`'s. pub fn all_libcalls() -> &'static [LibCall] { use LibCall::*; &[ Probestack, UdivI64, SdivI64, UremI64, SremI64, IshlI64, UshrI64, SshrI64, CeilF32, CeilF64, FloorF32, FloorF64, TruncF32, TruncF64, NearestF32, NearestF64, FmaF32, FmaF64, Memcpy, Memset, Memmove, Memcmp, ElfTlsGetAddr, ElfTlsGetOffset, ] } /// Get a [Signature] for the function targeted by this [LibCall]. pub fn signature(&self, call_conv: CallConv) -> Signature { use types::*; let mut sig = Signature::new(call_conv); match self { LibCall::UdivI64 | LibCall::SdivI64 | LibCall::UremI64 | LibCall::SremI64 | LibCall::IshlI64 | LibCall::UshrI64 | LibCall::SshrI64 => { sig.params.push(AbiParam::new(I64)); sig.params.push(AbiParam::new(I64)); sig.returns.push(AbiParam::new(I64)); } LibCall::CeilF32 | LibCall::FloorF32 | LibCall::TruncF32 | LibCall::NearestF32 => { sig.params.push(AbiParam::new(F32)); sig.returns.push(AbiParam::new(F32)); } LibCall::TruncF64 | LibCall::FloorF64 | LibCall::CeilF64 | LibCall::NearestF64 => { sig.params.push(AbiParam::new(F64)); sig.returns.push(AbiParam::new(F64)); } LibCall::FmaF32 | LibCall::FmaF64 => { let ty = if *self == LibCall::FmaF32 { F32 } else { F64 }; sig.params.push(AbiParam::new(ty)); sig.params.push(AbiParam::new(ty)); sig.params.push(AbiParam::new(ty)); sig.returns.push(AbiParam::new(ty)); } LibCall::Probestack | LibCall::Memcpy | LibCall::Memset | LibCall::Memmove | LibCall::Memcmp | LibCall::ElfTlsGetAddr | LibCall::ElfTlsGetOffset => unimplemented!(), } sig } } /// Get a function reference for the probestack function in `func`. /// /// If there is an existing reference, use it, otherwise make a new one. pub fn get_probestack_funcref(func: &mut Function) -> Option { find_funcref(LibCall::Probestack, func) } /// Get the existing function reference for `libcall` in `func` if it exists. fn find_funcref(libcall: LibCall, func: &Function) -> Option { // We're assuming that all libcall function decls are at the end. // If we get this wrong, worst case we'll have duplicate libcall decls which is harmless. for (fref, func_data) in func.dfg.ext_funcs.iter().rev() { match func_data.name { ExternalName::LibCall(lc) => { if lc == libcall { return Some(fref); } } _ => break, } } None } #[cfg(test)] mod tests { use super::*; use alloc::string::ToString; #[test] fn display() { assert_eq!(LibCall::CeilF32.to_string(), "CeilF32"); assert_eq!(LibCall::NearestF64.to_string(), "NearestF64"); } #[test] fn parsing() { assert_eq!("FloorF32".parse(), Ok(LibCall::FloorF32)); } #[test] fn all_libcalls_to_from_string() { for &libcall in LibCall::all_libcalls() { assert_eq!(libcall.to_string().parse(), Ok(libcall)); } } }