pub const CARRIED_C_FUNCTIONS: &[u8; 8] = b"WRPCcfun";
pub const HAND_LINKED_LIBC: [&str; 8] = ["abs", "strlen", "atoi", "atol", "atof", "strcmp", "strncmp", "rand"];
const FIELD_SEPARATOR: char = '\t';
const LIST_SEPARATOR: char = ',';
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum CPointer {
Text,
Memory,
Handle,
Out,
Struct,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ParamType {
I32,
I64,
F32,
F64,
Ptr,
Handle,
Out,
Bytes,
Length,
StructIn,
StructsIn,
StructOut,
ErrorOut,
StructsOut,
}
impl ParamType {
pub fn is_passed(self) -> bool {
!matches!(
self,
ParamType::Out | ParamType::ErrorOut | ParamType::Length | ParamType::StructOut | ParamType::StructsOut
)
}
pub fn is_struct_output(self) -> bool {
matches!(self, ParamType::StructOut | ParamType::StructsOut)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum RetType {
Void,
I32,
I64,
F32,
F64,
Bool,
}
const PARAM_NAMES: [(ParamType, &str); 14] = [
(ParamType::I32, "i32"),
(ParamType::I64, "i64"),
(ParamType::F32, "f32"),
(ParamType::F64, "f64"),
(ParamType::Ptr, "ptr"),
(ParamType::Handle, "handle"),
(ParamType::Out, "out"),
(ParamType::Bytes, "bytes"),
(ParamType::Length, "length"),
(ParamType::StructIn, "struct_in"),
(ParamType::StructsIn, "structs_in"),
(ParamType::StructOut, "struct_out"),
(ParamType::StructsOut, "structs_out"),
(ParamType::ErrorOut, "error_out"),
];
const RET_NAMES: [(RetType, &str); 6] = [
(RetType::Void, "void"),
(RetType::I32, "i32"),
(RetType::I64, "i64"),
(RetType::F32, "f32"),
(RetType::F64, "f64"),
(RetType::Bool, "bool"),
];
const RESULT_NAMES: [(Option<CPointer>, &str); 4] = [
(None, "number"),
(Some(CPointer::Text), "text"),
(Some(CPointer::Handle), "handle"),
(Some(CPointer::Struct), "struct"),
];
const LAYOUT_SEPARATOR: char = ';';
fn name_of<T: PartialEq + Copy>(names: &[(T, &'static str)], value: T) -> &'static str {
names.iter().find(|(known, _)| *known == value).map_or("?", |(_, name)| name)
}
fn value_of<T: Copy>(names: &[(T, &str)], name: &str) -> Option<T> {
names.iter().find(|(_, known)| *known == name).map(|(value, _)| *value)
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CFunction {
pub module: String,
pub library: String,
pub name: String,
pub param_types: Vec<ParamType>,
pub result: Option<CPointer>,
pub ret_type: RetType,
pub out_pointee: Option<String>,
pub layouts: Vec<String>,
pub failure: Failure,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Failure {
None,
NotNull,
False,
}
const FAILURE_NAMES: [(Failure, &str); 3] =
[(Failure::None, ""), (Failure::NotNull, "not_null"), (Failure::False, "false")];
pub fn is_error_type(c_type: &str) -> bool {
let lower = c_type.to_lowercase();
["error", "trap", "status", "exception"].iter().any(|word| lower.contains(word))
}
impl CFunction {
pub fn line(&self) -> String {
let params: Vec<&str> = self.param_types.iter().map(|param| name_of(&PARAM_NAMES, *param)).collect();
[
self.module.as_str(),
&self.library,
&self.name,
¶ms.join(&LIST_SEPARATOR.to_string()),
name_of(&RET_NAMES, self.ret_type),
name_of(&RESULT_NAMES, self.result),
self.out_pointee.as_deref().unwrap_or_default(),
&self.layouts.join(&LAYOUT_SEPARATOR.to_string()),
name_of(&FAILURE_NAMES, self.failure),
]
.join(&FIELD_SEPARATOR.to_string())
}
pub fn of_line(line: &str) -> Option<CFunction> {
let [module, library, name, params, ret_type, result, out_pointee, layouts, failure] =
line.split(FIELD_SEPARATOR).collect::<Vec<_>>().try_into().ok()?;
let param_types = params
.split(LIST_SEPARATOR)
.filter(|param| !param.is_empty())
.map(|param| value_of(&PARAM_NAMES, param))
.collect::<Option<_>>()?;
Some(CFunction {
module: module.to_string(),
library: library.to_string(),
name: name.to_string(),
param_types,
result: value_of(&RESULT_NAMES, result)?,
ret_type: value_of(&RET_NAMES, ret_type)?,
out_pointee: (!out_pointee.is_empty()).then(|| out_pointee.to_string()),
layouts: layouts.split(LAYOUT_SEPARATOR).filter(|_| !layouts.is_empty()).map(str::to_string).collect(),
failure: value_of(&FAILURE_NAMES, failure)?,
})
}
}
pub fn with_c_functions(functions: &[CFunction], machine_code: &[u8]) -> Vec<u8> {
if functions.is_empty() {
return machine_code.to_vec();
}
let table = functions.iter().map(CFunction::line).collect::<Vec<_>>().join("\n");
[CARRIED_C_FUNCTIONS.as_slice(), &(table.len() as u64).to_le_bytes(), table.as_bytes(), machine_code].concat()
}
pub fn split_c_functions(carried: &[u8]) -> Result<(Vec<CFunction>, &[u8]), String> {
let Some(rest) = carried.strip_prefix(CARRIED_C_FUNCTIONS.as_slice()) else {
return Ok((vec![], carried));
};
let damaged = || "the executable's table of C functions is damaged".to_string();
let (length, rest) = rest.split_first_chunk::<8>().ok_or_else(damaged)?;
let (table, machine_code) = rest.split_at_checked(u64::from_le_bytes(*length) as usize).ok_or_else(damaged)?;
let table = std::str::from_utf8(table).map_err(|_| damaged())?;
let functions = table.lines().map(CFunction::of_line).collect::<Option<_>>().ok_or_else(damaged)?;
Ok((functions, machine_code))
}