use std::collections::HashMap;
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CallingConvention {
C,
System,
Stdcall,
Fastcall,
Vectorcall,
Thiscall,
Win64,
SysV64,
Aapcs,
AArch64,
Wasm,
Rust,
Quanta,
}
impl CallingConvention {
pub fn llvm_cc(&self) -> u32 {
match self {
CallingConvention::C => 0, CallingConvention::Fastcall => 65, CallingConvention::Stdcall => 64, CallingConvention::Thiscall => 70, CallingConvention::Vectorcall => 80, CallingConvention::Win64 => 79, CallingConvention::SysV64 => 78, CallingConvention::Aapcs => 67, CallingConvention::AArch64 => 0, CallingConvention::Wasm => 0, CallingConvention::System => 0, CallingConvention::Rust => 0, CallingConvention::Quanta => 0, }
}
pub fn llvm_str(&self) -> &'static str {
match self {
CallingConvention::C => "ccc",
CallingConvention::Fastcall => "x86_fastcallcc",
CallingConvention::Stdcall => "x86_stdcallcc",
CallingConvention::Thiscall => "x86_thiscallcc",
CallingConvention::Vectorcall => "x86_vectorcallcc",
CallingConvention::Win64 => "win64cc",
CallingConvention::SysV64 => "x86_64_sysvcc",
CallingConvention::Aapcs => "aapcscc",
CallingConvention::AArch64 => "ccc",
CallingConvention::Wasm => "ccc",
CallingConvention::System => "ccc",
CallingConvention::Rust => "ccc",
CallingConvention::Quanta => "ccc",
}
}
pub fn from_str(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"c" | "cdecl" => Some(CallingConvention::C),
"system" => Some(CallingConvention::System),
"stdcall" => Some(CallingConvention::Stdcall),
"fastcall" => Some(CallingConvention::Fastcall),
"vectorcall" => Some(CallingConvention::Vectorcall),
"thiscall" => Some(CallingConvention::Thiscall),
"win64" => Some(CallingConvention::Win64),
"sysv64" | "sysv" => Some(CallingConvention::SysV64),
"aapcs" => Some(CallingConvention::Aapcs),
"aarch64" => Some(CallingConvention::AArch64),
"wasm" => Some(CallingConvention::Wasm),
"rust" => Some(CallingConvention::Rust),
"quanta" => Some(CallingConvention::Quanta),
_ => None,
}
}
}
impl Default for CallingConvention {
fn default() -> Self {
CallingConvention::C
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum CType {
Void,
Char,
UChar,
Short,
UShort,
Int,
UInt,
Long,
ULong,
LongLong,
ULongLong,
Float,
Double,
LongDouble,
SizeT,
SSizeT,
IntPtrT,
UIntPtrT,
PtrDiffT,
Bool,
Int8,
UInt8,
Int16,
UInt16,
Int32,
UInt32,
Int64,
UInt64,
Int128,
UInt128,
Ptr(Box<CType>),
ConstPtr(Box<CType>),
Array(Box<CType>, usize),
FnPtr(Box<CFunctionSignature>),
Struct(Arc<str>),
Union(Arc<str>),
Enum(Arc<str>),
Opaque(Arc<str>),
}
impl CType {
pub fn size(&self, ptr_size: usize) -> Option<usize> {
match self {
CType::Void => Some(0),
CType::Char | CType::UChar | CType::Bool | CType::Int8 | CType::UInt8 => Some(1),
CType::Short | CType::UShort | CType::Int16 | CType::UInt16 => Some(2),
CType::Int | CType::UInt | CType::Int32 | CType::UInt32 | CType::Float => Some(4),
CType::Long | CType::ULong => {
if ptr_size == 8 {
Some(8) } else {
Some(4)
}
}
CType::LongLong | CType::ULongLong | CType::Int64 | CType::UInt64 | CType::Double => {
Some(8)
}
CType::LongDouble => Some(16), CType::Int128 | CType::UInt128 => Some(16),
CType::SizeT | CType::SSizeT | CType::IntPtrT | CType::UIntPtrT | CType::PtrDiffT => {
Some(ptr_size)
}
CType::Ptr(_) | CType::ConstPtr(_) | CType::FnPtr(_) => Some(ptr_size),
CType::Array(elem, count) => elem.size(ptr_size).map(|s| s * count),
CType::Struct(_) | CType::Union(_) | CType::Enum(_) | CType::Opaque(_) => None, }
}
pub fn align(&self, ptr_size: usize) -> Option<usize> {
match self {
CType::Void => Some(1),
CType::Char | CType::UChar | CType::Bool | CType::Int8 | CType::UInt8 => Some(1),
CType::Short | CType::UShort | CType::Int16 | CType::UInt16 => Some(2),
CType::Int | CType::UInt | CType::Int32 | CType::UInt32 | CType::Float => Some(4),
CType::Long | CType::ULong => {
if ptr_size == 8 {
Some(8)
} else {
Some(4)
}
}
CType::LongLong | CType::ULongLong | CType::Int64 | CType::UInt64 | CType::Double => {
Some(8)
}
CType::LongDouble => Some(16),
CType::Int128 | CType::UInt128 => Some(16),
CType::SizeT | CType::SSizeT | CType::IntPtrT | CType::UIntPtrT | CType::PtrDiffT => {
Some(ptr_size)
}
CType::Ptr(_) | CType::ConstPtr(_) | CType::FnPtr(_) => Some(ptr_size),
CType::Array(elem, _) => elem.align(ptr_size),
CType::Struct(_) | CType::Union(_) | CType::Enum(_) | CType::Opaque(_) => None,
}
}
pub fn is_pointer(&self) -> bool {
matches!(self, CType::Ptr(_) | CType::ConstPtr(_) | CType::FnPtr(_))
}
pub fn is_integer(&self) -> bool {
matches!(
self,
CType::Char
| CType::UChar
| CType::Short
| CType::UShort
| CType::Int
| CType::UInt
| CType::Long
| CType::ULong
| CType::LongLong
| CType::ULongLong
| CType::Bool
| CType::Int8
| CType::UInt8
| CType::Int16
| CType::UInt16
| CType::Int32
| CType::UInt32
| CType::Int64
| CType::UInt64
| CType::Int128
| CType::UInt128
| CType::SizeT
| CType::SSizeT
| CType::IntPtrT
| CType::UIntPtrT
| CType::PtrDiffT
)
}
pub fn is_float(&self) -> bool {
matches!(self, CType::Float | CType::Double | CType::LongDouble)
}
pub fn is_signed(&self) -> bool {
matches!(
self,
CType::Char
| CType::Short
| CType::Int
| CType::Long
| CType::LongLong
| CType::Int8
| CType::Int16
| CType::Int32
| CType::Int64
| CType::Int128
| CType::SSizeT
| CType::IntPtrT
| CType::PtrDiffT
| CType::Float
| CType::Double
| CType::LongDouble
)
}
pub fn llvm_type(&self, ptr_size: usize) -> String {
match self {
CType::Void => "void".to_string(),
CType::Char | CType::UChar | CType::Bool | CType::Int8 | CType::UInt8 => {
"i8".to_string()
}
CType::Short | CType::UShort | CType::Int16 | CType::UInt16 => "i16".to_string(),
CType::Int | CType::UInt | CType::Int32 | CType::UInt32 => "i32".to_string(),
CType::Long | CType::ULong => {
if ptr_size == 8 {
"i64".to_string()
} else {
"i32".to_string()
}
}
CType::LongLong | CType::ULongLong | CType::Int64 | CType::UInt64 => "i64".to_string(),
CType::Int128 | CType::UInt128 => "i128".to_string(),
CType::Float => "float".to_string(),
CType::Double => "double".to_string(),
CType::LongDouble => "x86_fp80".to_string(),
CType::SizeT | CType::SSizeT | CType::IntPtrT | CType::UIntPtrT | CType::PtrDiffT => {
format!("i{}", ptr_size * 8)
}
CType::Ptr(inner) | CType::ConstPtr(inner) => {
format!("{}*", inner.llvm_type(ptr_size))
}
CType::FnPtr(_) => "ptr".to_string(),
CType::Array(elem, count) => {
format!("[{} x {}]", count, elem.llvm_type(ptr_size))
}
CType::Struct(name) => format!("%struct.{}", name),
CType::Union(name) => format!("%union.{}", name),
CType::Enum(name) => format!("%enum.{}", name),
CType::Opaque(name) => format!("%{}", name),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CFunctionSignature {
pub return_type: CType,
pub params: Vec<CType>,
pub param_names: Vec<Option<Arc<str>>>,
pub is_variadic: bool,
pub calling_conv: CallingConvention,
}
impl CFunctionSignature {
pub fn new(return_type: CType, params: Vec<CType>) -> Self {
let param_names = vec![None; params.len()];
Self {
return_type,
params,
param_names,
is_variadic: false,
calling_conv: CallingConvention::C,
}
}
pub fn variadic(mut self) -> Self {
self.is_variadic = true;
self
}
pub fn with_calling_conv(mut self, cc: CallingConvention) -> Self {
self.calling_conv = cc;
self
}
pub fn with_param_names(mut self, names: Vec<Option<Arc<str>>>) -> Self {
self.param_names = names;
self
}
}
#[derive(Debug, Clone)]
pub struct CStructField {
pub name: Arc<str>,
pub ty: CType,
pub offset: usize,
pub bit_offset: Option<u8>,
pub bit_width: Option<u8>,
}
#[derive(Debug, Clone)]
pub struct CStructDef {
pub name: Arc<str>,
pub fields: Vec<CStructField>,
pub size: usize,
pub align: usize,
pub packed: bool,
}
impl CStructDef {
pub fn new(
name: impl Into<Arc<str>>,
fields: Vec<(Arc<str>, CType)>,
packed: bool,
ptr_size: usize,
) -> Self {
let name = name.into();
let mut computed_fields = Vec::new();
let mut offset = 0usize;
let mut max_align = 1usize;
for (field_name, field_ty) in fields {
let field_align = if packed {
1
} else {
field_ty.align(ptr_size).unwrap_or(1)
};
let field_size = field_ty.size(ptr_size).unwrap_or(0);
if !packed {
offset = (offset + field_align - 1) & !(field_align - 1);
}
computed_fields.push(CStructField {
name: field_name,
ty: field_ty,
offset,
bit_offset: None,
bit_width: None,
});
offset += field_size;
max_align = max_align.max(field_align);
}
if !packed {
offset = (offset + max_align - 1) & !(max_align - 1);
}
Self {
name,
fields: computed_fields,
size: offset,
align: if packed { 1 } else { max_align },
packed,
}
}
pub fn get_field(&self, name: &str) -> Option<&CStructField> {
self.fields.iter().find(|f| f.name.as_ref() == name)
}
pub fn llvm_type_def(&self, ptr_size: usize) -> String {
let fields: Vec<String> = self
.fields
.iter()
.map(|f| f.ty.llvm_type(ptr_size))
.collect();
if self.packed {
format!("%struct.{} = type <{{ {} }}>", self.name, fields.join(", "))
} else {
format!("%struct.{} = type {{ {} }}", self.name, fields.join(", "))
}
}
}
#[derive(Debug, Clone)]
pub struct ExternFunction {
pub name: Arc<str>,
pub mangled_name: Option<Arc<str>>,
pub signature: CFunctionSignature,
pub library: Option<Arc<str>>,
pub weak: bool,
pub doc: Option<Arc<str>>,
}
impl ExternFunction {
pub fn new(name: impl Into<Arc<str>>, signature: CFunctionSignature) -> Self {
Self {
name: name.into(),
mangled_name: None,
signature,
library: None,
weak: false,
doc: None,
}
}
pub fn with_mangled_name(mut self, name: impl Into<Arc<str>>) -> Self {
self.mangled_name = Some(name.into());
self
}
pub fn with_library(mut self, lib: impl Into<Arc<str>>) -> Self {
self.library = Some(lib.into());
self
}
pub fn weak(mut self) -> Self {
self.weak = true;
self
}
pub fn symbol_name(&self) -> &str {
self.mangled_name.as_ref().unwrap_or(&self.name).as_ref()
}
pub fn llvm_declaration(&self, ptr_size: usize) -> String {
let ret_ty = self.signature.return_type.llvm_type(ptr_size);
let params: Vec<String> = self
.signature
.params
.iter()
.map(|p| p.llvm_type(ptr_size))
.collect();
let variadic = if self.signature.is_variadic {
", ..."
} else {
""
};
let linkage = if self.weak { "extern_weak " } else { "" };
let cc = self.signature.calling_conv.llvm_str();
format!(
"declare {} {} {} @{}({}{})",
linkage,
cc,
ret_ty,
self.symbol_name(),
params.join(", "),
variadic
)
}
}
#[derive(Debug, Default)]
pub struct FfiContext {
pub functions: HashMap<Arc<str>, ExternFunction>,
pub structs: HashMap<Arc<str>, CStructDef>,
pub typedefs: HashMap<Arc<str>, CType>,
pub libraries: Vec<LibraryLink>,
pub ptr_size: usize,
}
#[derive(Debug, Clone)]
pub struct LibraryLink {
pub name: Arc<str>,
pub path: Option<Arc<str>>,
pub kind: LinkKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LinkKind {
Dynamic,
Static,
Framework,
}
impl FfiContext {
pub fn new(ptr_size: usize) -> Self {
Self {
functions: HashMap::new(),
structs: HashMap::new(),
typedefs: HashMap::new(),
libraries: Vec::new(),
ptr_size,
}
}
pub fn add_function(&mut self, func: ExternFunction) {
self.functions.insert(func.name.clone(), func);
}
pub fn add_struct(&mut self, def: CStructDef) {
self.structs.insert(def.name.clone(), def);
}
pub fn add_typedef(&mut self, name: impl Into<Arc<str>>, ty: CType) {
self.typedefs.insert(name.into(), ty);
}
pub fn add_library(&mut self, name: impl Into<Arc<str>>, kind: LinkKind) {
self.libraries.push(LibraryLink {
name: name.into(),
path: None,
kind,
});
}
pub fn resolve_type(&self, ty: &CType) -> CType {
match ty {
CType::Opaque(name) => {
if let Some(resolved) = self.typedefs.get(name) {
self.resolve_type(resolved)
} else {
ty.clone()
}
}
_ => ty.clone(),
}
}
pub fn llvm_declarations(&self) -> String {
let mut output = String::new();
for def in self.structs.values() {
output.push_str(&def.llvm_type_def(self.ptr_size));
output.push('\n');
}
if !self.structs.is_empty() {
output.push('\n');
}
for func in self.functions.values() {
output.push_str(&func.llvm_declaration(self.ptr_size));
output.push('\n');
}
output
}
pub fn add_libc(&mut self) {
self.add_function(ExternFunction::new(
"malloc",
CFunctionSignature::new(CType::Ptr(Box::new(CType::Void)), vec![CType::SizeT]),
));
self.add_function(ExternFunction::new(
"calloc",
CFunctionSignature::new(
CType::Ptr(Box::new(CType::Void)),
vec![CType::SizeT, CType::SizeT],
),
));
self.add_function(ExternFunction::new(
"realloc",
CFunctionSignature::new(
CType::Ptr(Box::new(CType::Void)),
vec![CType::Ptr(Box::new(CType::Void)), CType::SizeT],
),
));
self.add_function(ExternFunction::new(
"free",
CFunctionSignature::new(CType::Void, vec![CType::Ptr(Box::new(CType::Void))]),
));
self.add_function(ExternFunction::new(
"memcpy",
CFunctionSignature::new(
CType::Ptr(Box::new(CType::Void)),
vec![
CType::Ptr(Box::new(CType::Void)),
CType::ConstPtr(Box::new(CType::Void)),
CType::SizeT,
],
),
));
self.add_function(ExternFunction::new(
"memmove",
CFunctionSignature::new(
CType::Ptr(Box::new(CType::Void)),
vec![
CType::Ptr(Box::new(CType::Void)),
CType::ConstPtr(Box::new(CType::Void)),
CType::SizeT,
],
),
));
self.add_function(ExternFunction::new(
"memset",
CFunctionSignature::new(
CType::Ptr(Box::new(CType::Void)),
vec![CType::Ptr(Box::new(CType::Void)), CType::Int, CType::SizeT],
),
));
self.add_function(ExternFunction::new(
"memcmp",
CFunctionSignature::new(
CType::Int,
vec![
CType::ConstPtr(Box::new(CType::Void)),
CType::ConstPtr(Box::new(CType::Void)),
CType::SizeT,
],
),
));
self.add_function(ExternFunction::new(
"strlen",
CFunctionSignature::new(CType::SizeT, vec![CType::ConstPtr(Box::new(CType::Char))]),
));
self.add_function(ExternFunction::new(
"strcpy",
CFunctionSignature::new(
CType::Ptr(Box::new(CType::Char)),
vec![
CType::Ptr(Box::new(CType::Char)),
CType::ConstPtr(Box::new(CType::Char)),
],
),
));
self.add_function(ExternFunction::new(
"strncpy",
CFunctionSignature::new(
CType::Ptr(Box::new(CType::Char)),
vec![
CType::Ptr(Box::new(CType::Char)),
CType::ConstPtr(Box::new(CType::Char)),
CType::SizeT,
],
),
));
self.add_function(ExternFunction::new(
"strcmp",
CFunctionSignature::new(
CType::Int,
vec![
CType::ConstPtr(Box::new(CType::Char)),
CType::ConstPtr(Box::new(CType::Char)),
],
),
));
self.add_function(ExternFunction::new(
"strncmp",
CFunctionSignature::new(
CType::Int,
vec![
CType::ConstPtr(Box::new(CType::Char)),
CType::ConstPtr(Box::new(CType::Char)),
CType::SizeT,
],
),
));
self.add_function(ExternFunction::new(
"printf",
CFunctionSignature::new(CType::Int, vec![CType::ConstPtr(Box::new(CType::Char))])
.variadic(),
));
self.add_function(ExternFunction::new(
"sprintf",
CFunctionSignature::new(
CType::Int,
vec![
CType::Ptr(Box::new(CType::Char)),
CType::ConstPtr(Box::new(CType::Char)),
],
)
.variadic(),
));
self.add_function(ExternFunction::new(
"snprintf",
CFunctionSignature::new(
CType::Int,
vec![
CType::Ptr(Box::new(CType::Char)),
CType::SizeT,
CType::ConstPtr(Box::new(CType::Char)),
],
)
.variadic(),
));
self.add_function(ExternFunction::new(
"puts",
CFunctionSignature::new(CType::Int, vec![CType::ConstPtr(Box::new(CType::Char))]),
));
self.add_function(ExternFunction::new(
"putchar",
CFunctionSignature::new(CType::Int, vec![CType::Int]),
));
self.add_function(ExternFunction::new(
"sin",
CFunctionSignature::new(CType::Double, vec![CType::Double]),
));
self.add_function(ExternFunction::new(
"cos",
CFunctionSignature::new(CType::Double, vec![CType::Double]),
));
self.add_function(ExternFunction::new(
"tan",
CFunctionSignature::new(CType::Double, vec![CType::Double]),
));
self.add_function(ExternFunction::new(
"sqrt",
CFunctionSignature::new(CType::Double, vec![CType::Double]),
));
self.add_function(ExternFunction::new(
"pow",
CFunctionSignature::new(CType::Double, vec![CType::Double, CType::Double]),
));
self.add_function(ExternFunction::new(
"exp",
CFunctionSignature::new(CType::Double, vec![CType::Double]),
));
self.add_function(ExternFunction::new(
"log",
CFunctionSignature::new(CType::Double, vec![CType::Double]),
));
self.add_function(ExternFunction::new(
"log10",
CFunctionSignature::new(CType::Double, vec![CType::Double]),
));
self.add_function(ExternFunction::new(
"fabs",
CFunctionSignature::new(CType::Double, vec![CType::Double]),
));
self.add_function(ExternFunction::new(
"floor",
CFunctionSignature::new(CType::Double, vec![CType::Double]),
));
self.add_function(ExternFunction::new(
"ceil",
CFunctionSignature::new(CType::Double, vec![CType::Double]),
));
self.add_function(ExternFunction::new(
"exit",
CFunctionSignature::new(CType::Void, vec![CType::Int]),
));
self.add_function(ExternFunction::new(
"abort",
CFunctionSignature::new(CType::Void, vec![]),
));
self.add_function(ExternFunction::new(
"getenv",
CFunctionSignature::new(
CType::Ptr(Box::new(CType::Char)),
vec![CType::ConstPtr(Box::new(CType::Char))],
),
));
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StringEncoding {
Utf8,
Utf16Le,
Utf16Be,
Utf32Le,
Ascii,
Latin1,
}
pub fn to_c_string(s: &str) -> Vec<u8> {
let mut bytes = s.as_bytes().to_vec();
bytes.push(0);
bytes
}
pub unsafe fn from_c_string(ptr: *const i8) -> String {
if ptr.is_null() {
return String::new();
}
let mut len = 0;
while unsafe { *ptr.add(len) } != 0 {
len += 1;
}
let slice = unsafe { std::slice::from_raw_parts(ptr as *const u8, len) };
String::from_utf8_lossy(slice).into_owned()
}
pub fn utf16_to_utf8(utf16: &[u16]) -> String {
String::from_utf16_lossy(utf16)
}
pub fn utf8_to_utf16(s: &str) -> Vec<u16> {
s.encode_utf16().collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_calling_convention_llvm() {
assert_eq!(CallingConvention::C.llvm_str(), "ccc");
assert_eq!(CallingConvention::Stdcall.llvm_str(), "x86_stdcallcc");
assert_eq!(CallingConvention::Fastcall.llvm_str(), "x86_fastcallcc");
}
#[test]
fn test_ctype_size() {
assert_eq!(CType::Int8.size(8), Some(1));
assert_eq!(CType::Int32.size(8), Some(4));
assert_eq!(CType::Int64.size(8), Some(8));
assert_eq!(CType::Ptr(Box::new(CType::Void)).size(8), Some(8));
assert_eq!(CType::Ptr(Box::new(CType::Void)).size(4), Some(4));
}
#[test]
fn test_struct_layout() {
let fields = vec![
(Arc::from("a"), CType::Int32),
(Arc::from("b"), CType::Int64),
(Arc::from("c"), CType::Int8),
];
let def = CStructDef::new("TestStruct", fields, false, 8);
assert_eq!(def.fields[0].offset, 0); assert_eq!(def.fields[1].offset, 8); assert_eq!(def.fields[2].offset, 16); assert_eq!(def.size, 24); assert_eq!(def.align, 8);
}
#[test]
fn test_packed_struct_layout() {
let fields = vec![
(Arc::from("a"), CType::Int32),
(Arc::from("b"), CType::Int64),
(Arc::from("c"), CType::Int8),
];
let def = CStructDef::new("PackedStruct", fields, true, 8);
assert_eq!(def.fields[0].offset, 0); assert_eq!(def.fields[1].offset, 4); assert_eq!(def.fields[2].offset, 12); assert_eq!(def.size, 13); assert_eq!(def.align, 1);
}
#[test]
fn test_extern_function() {
let sig = CFunctionSignature::new(CType::Int, vec![CType::ConstPtr(Box::new(CType::Char))])
.variadic();
let func = ExternFunction::new("printf", sig);
let decl = func.llvm_declaration(8);
assert!(decl.contains("declare"));
assert!(decl.contains("@printf"));
assert!(decl.contains("..."));
}
#[test]
fn test_to_c_string() {
let s = "Hello";
let c_str = to_c_string(s);
assert_eq!(c_str, vec![72, 101, 108, 108, 111, 0]);
}
#[test]
fn test_ffi_context_libc() {
let mut ctx = FfiContext::new(8);
ctx.add_libc();
assert!(ctx.functions.contains_key("malloc"));
assert!(ctx.functions.contains_key("free"));
assert!(ctx.functions.contains_key("printf"));
assert!(ctx.functions.contains_key("strlen"));
}
}