Cranelift: Add LibCall::Memcmp

The comment says the enum is "likely to grow" and the function's been in libc since C89, so hopefully this is ok.

I'd like to use it for emitting things like array equality.
This commit is contained in:
Scott McMurray
2021-05-31 12:27:29 -07:00
parent d1e9a7840e
commit c266f7f4c3
3 changed files with 112 additions and 0 deletions

View File

@@ -56,6 +56,8 @@ pub enum LibCall {
Memset, Memset,
/// libc.memmove /// libc.memmove
Memmove, Memmove,
/// libc.memcmp
Memcmp,
/// Elf __tls_get_addr /// Elf __tls_get_addr
ElfTlsGetAddr, ElfTlsGetAddr,
@@ -92,6 +94,7 @@ impl FromStr for LibCall {
"Memcpy" => Ok(Self::Memcpy), "Memcpy" => Ok(Self::Memcpy),
"Memset" => Ok(Self::Memset), "Memset" => Ok(Self::Memset),
"Memmove" => Ok(Self::Memmove), "Memmove" => Ok(Self::Memmove),
"Memcmp" => Ok(Self::Memcmp),
"ElfTlsGetAddr" => Ok(Self::ElfTlsGetAddr), "ElfTlsGetAddr" => Ok(Self::ElfTlsGetAddr),
_ => Err(()), _ => Err(()),
@@ -157,6 +160,7 @@ impl LibCall {
Memcpy, Memcpy,
Memset, Memset,
Memmove, Memmove,
Memcmp,
ElfTlsGetAddr, ElfTlsGetAddr,
] ]
} }
@@ -201,4 +205,11 @@ mod tests {
fn parsing() { fn parsing() {
assert_eq!("FloorF32".parse(), Ok(LibCall::FloorF32)); 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));
}
}
} }

View File

@@ -804,6 +804,42 @@ impl<'a> FunctionBuilder<'a> {
self.ins().call(libc_memmove, &[dest, source, size]); self.ins().call(libc_memmove, &[dest, source, size]);
} }
/// Calls libc.memcmp
///
/// Compares `size` bytes from memory starting at `left` to memory starting
/// at `right`. Returns `0` if all `n` bytes are equal. If the first difference
/// is at offset `i`, returns a positive integer if `ugt(left[i], right[i])`
/// and a negative integer if `ult(left[i], right[i])`.
///
/// Returns a C `int`, which is currently always [`types::I32`].
pub fn call_memcmp(
&mut self,
config: TargetFrontendConfig,
left: Value,
right: Value,
size: Value,
) -> Value {
let pointer_type = config.pointer_type();
let signature = {
let mut s = Signature::new(config.default_call_conv);
s.params.reserve(3);
s.params.push(AbiParam::new(pointer_type));
s.params.push(AbiParam::new(pointer_type));
s.params.push(AbiParam::new(pointer_type));
s.returns.push(AbiParam::new(types::I32));
self.import_signature(s)
};
let libc_memcmp = self.import_function(ExtFuncData {
name: ExternalName::LibCall(LibCall::Memcmp),
signature,
colocated: false,
});
let call = self.ins().call(libc_memcmp, &[left, right, size]);
self.func.dfg.first_result(call)
}
} }
fn greatest_divisible_power_of_two(size: u64) -> u64 { fn greatest_divisible_power_of_two(size: u64) -> u64 {
@@ -1212,6 +1248,70 @@ block0:
); );
} }
#[test]
fn memcmp() {
use core::str::FromStr;
use cranelift_codegen::{isa, settings};
let shared_builder = settings::builder();
let shared_flags = settings::Flags::new(shared_builder);
let triple =
::target_lexicon::Triple::from_str("x86_64").expect("Couldn't create x86_64 triple");
let target = isa::lookup(triple)
.ok()
.map(|b| b.finish(shared_flags))
.expect("This test requires x86_64 support.");
let mut sig = Signature::new(target.default_call_conv());
sig.returns.push(AbiParam::new(I32));
let mut fn_ctx = FunctionBuilderContext::new();
let mut func = Function::with_name_signature(ExternalName::testcase("sample"), sig);
{
let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
let block0 = builder.create_block();
let x = Variable::new(0);
let y = Variable::new(1);
let z = Variable::new(2);
builder.declare_var(x, target.pointer_type());
builder.declare_var(y, target.pointer_type());
builder.declare_var(z, target.pointer_type());
builder.append_block_params_for_function_params(block0);
builder.switch_to_block(block0);
let left = builder.use_var(x);
let right = builder.use_var(y);
let size = builder.use_var(z);
let cmp = builder.call_memcmp(target.frontend_config(), left, right, size);
builder.ins().return_(&[cmp]);
builder.seal_all_blocks();
builder.finalize();
}
assert_eq!(
func.display(None).to_string(),
"function %sample() -> i32 system_v {
sig0 = (i64, i64, i64) -> i32 system_v
fn0 = %Memcmp sig0
block0:
v6 = iconst.i64 0
v2 -> v6
v5 = iconst.i64 0
v1 -> v5
v4 = iconst.i64 0
v0 -> v4
v3 = call fn0(v0, v1, v2)
return v3
}
"
);
}
#[test] #[test]
fn undef_vector_vars() { fn undef_vector_vars() {
let mut sig = Signature::new(CallConv::SystemV); let mut sig = Signature::new(CallConv::SystemV);

View File

@@ -73,6 +73,7 @@ pub fn default_libcall_names() -> Box<dyn Fn(ir::LibCall) -> String + Send + Syn
ir::LibCall::Memcpy => "memcpy".to_owned(), ir::LibCall::Memcpy => "memcpy".to_owned(),
ir::LibCall::Memset => "memset".to_owned(), ir::LibCall::Memset => "memset".to_owned(),
ir::LibCall::Memmove => "memmove".to_owned(), ir::LibCall::Memmove => "memmove".to_owned(),
ir::LibCall::Memcmp => "memcmp".to_owned(),
ir::LibCall::ElfTlsGetAddr => "__tls_get_addr".to_owned(), ir::LibCall::ElfTlsGetAddr => "__tls_get_addr".to_owned(),
}) })