holey-bytes/hbasm/src/ins.rs

322 lines
11 KiB
Rust
Raw Normal View History

2024-02-22 16:57:29 -06:00
//! Functions for inserting instructions
//!
//! Most of the code you see is just metaprogramming stuff.
//! This ensures that adding new instructions won't need any
//! specific changes and consistent behaviour.
//!
//! > I tried to comment stuff here, but I meanwhile forgor how it works.
//!
//! — Erin
use {
2023-10-27 20:29:02 -05:00
crate::object::Object,
2024-02-14 04:45:58 -06:00
rhai::{FuncRegistration, Module},
2023-10-27 20:29:02 -05:00
std::{cell::RefCell, rc::Rc},
};
2024-02-22 16:57:29 -06:00
/// Operand types and their insertions
2024-01-31 10:54:38 -06:00
pub mod optypes {
2023-10-27 20:29:02 -05:00
use {
crate::{
label::UnboundLabel,
object::{Object, RelocKey, RelocType, SymbolRef},
},
rhai::{Dynamic, EvalAltResult, ImmutableString, Position},
};
2024-02-22 16:57:29 -06:00
// These types represent operand types to be inserted
2023-10-27 20:29:02 -05:00
pub type R = u8;
pub type B = i8;
pub type H = i16;
pub type W = i32;
pub type D = i64;
2023-10-27 20:29:02 -05:00
pub type A = Dynamic;
pub type O = Dynamic;
pub type P = Dynamic;
2024-02-22 16:57:29 -06:00
/// Insert relocation into code
///
/// - If integer, just write it to the code
/// - Otherwise insert entry into relocation table
/// and fill zeroes
2023-10-27 20:29:02 -05:00
pub fn insert_reloc(
obj: &mut Object,
ty: RelocType,
val: &Dynamic,
) -> Result<(), EvalAltResult> {
match () {
2024-02-22 16:57:29 -06:00
// Direct references insert directly to table
2023-10-27 20:29:02 -05:00
_ if val.is::<SymbolRef>() => {
obj.relocation(RelocKey::Symbol(val.clone_cast::<SymbolRef>().0), ty)
}
_ if val.is::<UnboundLabel>() => {
obj.relocation(RelocKey::Symbol(val.clone_cast::<UnboundLabel>().0), ty)
}
_ if val.is::<DataRef>() => {
obj.relocation(RelocKey::Symbol(val.clone_cast::<DataRef>().symbol.0), ty)
}
2024-02-22 16:57:29 -06:00
// String (indirect) reference
2023-10-27 20:29:02 -05:00
_ if val.is_string() => {
obj.relocation(RelocKey::Label(val.clone_cast::<ImmutableString>()), ty)
}
2024-02-22 16:57:29 -06:00
// Manual offset
2023-10-27 20:29:02 -05:00
_ if val.is_int() => {
let int = val.clone_cast::<i64>();
match ty {
RelocType::Rel32 => obj.sections.text.extend((int as i32).to_le_bytes()),
RelocType::Rel16 => obj.sections.text.extend((int as i16).to_le_bytes()),
RelocType::Abs64 => obj.sections.text.extend(int.to_le_bytes()),
}
}
2024-02-22 16:57:29 -06:00
2023-10-27 20:29:02 -05:00
_ => {
return Err(EvalAltResult::ErrorMismatchDataType(
2024-02-22 16:57:29 -06:00
"SymbolRef, UnboundLabel, String or Int".to_owned(),
2023-10-27 20:29:02 -05:00
val.type_name().to_owned(),
Position::NONE,
))
}
}
2023-10-27 20:29:02 -05:00
Ok(())
}
2024-02-22 16:57:29 -06:00
/// Generate macro for inserting item into the output object
///
/// Pre-defines inserts for absolute address and relative offsets.
/// These are inserted with function [`insert_reloc`]
/// # le_bytes
/// `gen_insert!(le_bytes: [B, …]);`
///
/// Takes sequence of operand types which should be inserted
/// by invoking `to_le_bytes` method on it.
2023-10-27 20:29:02 -05:00
macro_rules! gen_insert {
(le_bytes: [$($lety:ident),* $(,)?]) => {
2024-02-22 16:57:29 -06:00
/// `insert!($thing, $obj, $type)` where
/// - `$thing`: Value you want to insert
/// - `$obj`: Code object
/// - `$type`: Type of inserted value
///
/// Eg. `insert!(69_u8, obj, B);`
2023-10-27 20:29:02 -05:00
macro_rules! insert {
$(($thing:expr, $obj: expr, $lety) => {
$obj.sections.text.extend($thing.to_le_bytes());
};)*
($thing:expr, $obj:expr, A) => {
$crate::ins::optypes::insert_reloc(
$obj,
$crate::object::RelocType::Abs64,
$thing
)?
};
($thing:expr, $obj:expr, O) => {
$crate::ins::optypes::insert_reloc(
$obj,
$crate::object::RelocType::Rel32,
$thing
)?
};
($thing:expr, $obj:expr, P) => {
$crate::ins::optypes::insert_reloc(
$obj,
$crate::object::RelocType::Rel16,
$thing
)?
};
}
};
}
gen_insert!(le_bytes: [R, B, H, W, D]);
#[allow(clippy::single_component_path_imports)]
pub(super) use insert;
use crate::data::DataRef;
}
2024-02-22 16:57:29 -06:00
/// Rhai Types (types for function parameters as Rhai uses only 64bit signed integers)
2024-01-31 10:54:38 -06:00
pub mod rity {
2023-10-27 20:29:02 -05:00
pub use super::optypes::{A, O, P, R};
pub type B = i64;
pub type H = i64;
pub type W = i64;
pub type D = i64;
}
2024-02-22 16:57:29 -06:00
/// Generic instruction (instruction of certain operands type) inserts
2024-01-31 10:54:38 -06:00
pub mod generic {
2023-10-27 20:29:02 -05:00
use {crate::object::Object, rhai::EvalAltResult};
2023-10-27 20:29:02 -05:00
pub(super) fn convert_op<A, B>(from: A) -> Result<B, EvalAltResult>
where
B: TryFrom<A>,
<B as TryFrom<A>>::Error: std::error::Error + Sync + Send + 'static,
{
B::try_from(from).map_err(|e| {
EvalAltResult::ErrorSystem("Data conversion error".to_owned(), Box::new(e))
})
}
2024-02-22 16:57:29 -06:00
/// Generate opcode-generic instruction insert macro
2023-10-27 20:29:02 -05:00
macro_rules! gen_ins {
($($($name:ident : $ty:ty),*;)*) => {
paste::paste! {
2024-02-22 16:57:29 -06:00
$(
/// Instruction-generic opcode insertion function
/// - `obj`: Code object
/// - `opcode`: opcode, not checked if valid for instruction type
/// - … for operands
#[inline]
pub fn [<$($ty:lower)*>](
obj: &mut Object,
opcode: u8,
$($name: $crate::ins::optypes::$ty),*,
) -> Result<(), EvalAltResult> {
// Push opcode
obj.sections.text.push(opcode);
// Insert based on type
$($crate::ins::optypes::insert!(&$name, obj, $ty);)*
Ok(())
}
)*
2023-10-27 20:29:02 -05:00
2024-02-22 16:57:29 -06:00
/// Generate Rhai opcode-specific instruction insertion functions
///
/// `gen_ins_fn!($obj, $opcode, $optype);` where:
/// - `$obj`: Code object
/// - `$opcode`: Opcode value
2023-10-27 20:29:02 -05:00
macro_rules! gen_ins_fn {
2024-02-14 04:45:58 -06:00
$(
($obj:expr, $opcode:expr, [<$($ty)*>]) => {
2024-02-22 16:57:29 -06:00
// Opcode-specific insertion function
// - Parameters = operands
2024-02-14 04:45:58 -06:00
move |$($name: $crate::ins::rity::$ty),*| {
2024-02-22 16:57:29 -06:00
// Invoke generic function
2024-02-14 04:45:58 -06:00
$crate::ins::generic::[<$($ty:lower)*>](
&mut *$obj.borrow_mut(),
$opcode,
$(
2024-02-22 16:57:29 -06:00
// Convert to desired type (from Rhai-provided values)
$crate::ins::generic::convert_op::<
2024-02-14 04:45:58 -06:00
_,
$crate::ins::optypes::$ty
>($name)?
),*
)?;
Ok(())
}
};
2024-02-22 16:57:29 -06:00
// Internal-use: count args
2024-02-14 04:45:58 -06:00
(@arg_count [<$($ty)*>]) => {
{ ["", $(stringify!($ty)),*].len() - 1 }
};
)*
2023-10-27 20:29:02 -05:00
2024-02-22 16:57:29 -06:00
// Specialisation for no-operand instructions
2023-10-27 20:29:02 -05:00
($obj:expr, $opcode:expr, N) => {
move || {
$crate::ins::generic::n(&mut *$obj.borrow_mut(), $opcode);
Ok(())
}
};
2024-02-14 04:45:58 -06:00
2024-02-22 16:57:29 -06:00
// Internal-use specialisation: no-operand instructions
2024-02-14 04:45:58 -06:00
(@arg_count N) => {
{ 0 }
};
2023-10-27 20:29:02 -05:00
}
}
2023-10-27 20:29:02 -05:00
};
}
2024-02-22 16:57:29 -06:00
/// Specialisation for no-operand instructions simply just push opcode
2023-10-27 20:29:02 -05:00
#[inline]
pub fn n(obj: &mut Object, opcode: u8) {
obj.sections.text.push(opcode);
}
2024-02-22 16:57:29 -06:00
// Generate opcode-generic instruction inserters
// (operand identifiers are arbitrary)
//
// New instruction types have to be added manually here
2023-10-27 20:29:02 -05:00
gen_ins! {
o0: R, o1: R;
o0: R, o1: R, o2: R;
o0: R, o1: R, o2: R, o3: R;
o0: R, o1: R, o2: B;
o0: R, o1: R, o2: H;
o0: R, o1: R, o2: W;
o0: R, o1: R, o2: D;
o0: R, o1: B;
o0: R, o1: H;
o0: R, o1: W;
o0: R, o1: D;
o0: R, o1: R, o2: A;
o0: R, o1: R, o2: A, o3: H;
o0: R, o1: R, o2: O, o3: H;
o0: R, o1: R, o2: P, o3: H;
o0: R, o1: R, o2: O;
o0: R, o1: R, o2: P;
o0: O;
o0: P;
}
2023-10-27 20:29:02 -05:00
#[allow(clippy::single_component_path_imports)]
pub(super) use gen_ins_fn;
}
2024-02-22 16:57:29 -06:00
/// Generate instructions from instruction table
///
/// ```ignore
/// instructions!(($module, $obj) {
/// // Data from instruction table
/// $opcode, $mnemonic, $opty, $doc;
/// …
/// });
/// ```
/// - `$module`: Rhai module
/// - `$obj`: Code object
2023-10-27 20:29:02 -05:00
macro_rules! instructions {
(
($module:expr, $obj:expr $(,)?)
2024-02-14 04:45:58 -06:00
{ $($opcode:expr, $mnemonic:ident, $ops:tt, $doc:literal;)* }
2023-10-27 20:29:02 -05:00
) => {{
2024-04-16 08:34:12 -05:00
paste::paste! {
let (module, obj) = ($module, $obj);
$({
// Object is shared across all functions
let obj = Rc::clone(&obj);
2024-02-22 16:57:29 -06:00
2024-04-16 08:34:12 -05:00
// Register newly generated function for each instruction
FuncRegistration::new(stringify!([<$mnemonic:lower>]))
.with_namespace(rhai::FnNamespace::Global)
.set_into_module::<_, { generic::gen_ins_fn!(@arg_count $ops) }, false, _, true, _>(
module,
generic::gen_ins_fn!(
obj,
$opcode,
$ops
)
);
})*
}
2023-10-27 20:29:02 -05:00
}};
}
2024-02-22 16:57:29 -06:00
/// Setup instruction insertors
2023-10-27 20:29:02 -05:00
pub fn setup(module: &mut Module, obj: Rc<RefCell<Object>>) {
2024-02-22 16:57:29 -06:00
// Import instructions table and use it for generation
with_builtin_macros::with_builtin! {
let $spec = include_from_root!("../hbbytecode/instructions.in") in {
2023-10-27 20:29:02 -05:00
instructions!((module, obj) { $spec });
}
}
}