forked from AbleOS/holey-bytes
Documented hbasm
This commit is contained in:
parent
6c6e29479f
commit
0070016f74
|
@ -1,14 +1,21 @@
|
|||
use rhai::{CustomType, Engine, FuncRegistration, ImmutableString};
|
||||
//! Data section inserts
|
||||
|
||||
use {
|
||||
crate::{object::SymbolRef, SharedObject},
|
||||
rhai::Module,
|
||||
rhai::{CustomType, Engine, FuncRegistration, ImmutableString, Module},
|
||||
};
|
||||
|
||||
/// Generate insertions for data types
|
||||
///
|
||||
/// `gen_data_instructions!($module, $obj, [$type, …]);`
|
||||
/// - `$module`: Rhai module
|
||||
/// - `$obj`: Code object
|
||||
/// - `$type`: Type of single array item
|
||||
macro_rules! gen_data_insertions {
|
||||
($module:expr, $obj:expr, [$($ty:ident),* $(,)?] $(,)?) => {{
|
||||
let (module, obj) = ($module, $obj);
|
||||
$({
|
||||
// Clone object to each function
|
||||
let obj = ::std::rc::Rc::clone(obj);
|
||||
|
||||
FuncRegistration::new(stringify!($ty))
|
||||
|
@ -17,17 +24,23 @@ macro_rules! gen_data_insertions {
|
|||
let obj = &mut *obj.borrow_mut();
|
||||
let symbol = obj.symbol($crate::object::Section::Data);
|
||||
|
||||
// Reserve space for object so we don't resize it
|
||||
// all the time
|
||||
obj.sections
|
||||
.data
|
||||
.reserve(arr.len() * ::std::mem::size_of::<$ty>());
|
||||
|
||||
// For every item…
|
||||
for item in arr {
|
||||
// … try do conversions from i32 to desired type
|
||||
// and insert it.
|
||||
obj.sections.data.extend(
|
||||
match item.as_int() {
|
||||
Ok(num) => $ty::try_from(num).map_err(|_| "i64".to_owned()),
|
||||
Err(ty) => Err(ty.to_owned()),
|
||||
}
|
||||
.map_err(|err| {
|
||||
|
||||
::rhai::EvalAltResult::ErrorMismatchDataType(
|
||||
stringify!($ty).to_owned(),
|
||||
err,
|
||||
|
@ -47,6 +60,7 @@ macro_rules! gen_data_insertions {
|
|||
}};
|
||||
}
|
||||
|
||||
/// Reference to entry in data section
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct DataRef {
|
||||
pub symbol: SymbolRef,
|
||||
|
@ -67,6 +81,8 @@ pub fn module(engine: &mut Engine, obj: SharedObject) -> Module {
|
|||
|
||||
gen_data_insertions!(&mut module, &obj, [i8, i16, i32, i64]);
|
||||
|
||||
// Specialisation for strings, they should be
|
||||
// inserted as plain UTF-8 arrays
|
||||
FuncRegistration::new("str")
|
||||
.with_namespace(rhai::FnNamespace::Global)
|
||||
.set_into_module::<_, 1, false, _, true, _>(&mut module, move |s: ImmutableString| {
|
||||
|
|
105
hbasm/src/ins.rs
105
hbasm/src/ins.rs
|
@ -1,9 +1,20 @@
|
|||
//! 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 {
|
||||
crate::object::Object,
|
||||
rhai::{FuncRegistration, Module},
|
||||
std::{cell::RefCell, rc::Rc},
|
||||
};
|
||||
|
||||
/// Operand types and their insertions
|
||||
pub mod optypes {
|
||||
use {
|
||||
crate::{
|
||||
|
@ -13,6 +24,7 @@ pub mod optypes {
|
|||
rhai::{Dynamic, EvalAltResult, ImmutableString, Position},
|
||||
};
|
||||
|
||||
// These types represent operand types to be inserted
|
||||
pub type R = u8;
|
||||
pub type B = i8;
|
||||
pub type H = i16;
|
||||
|
@ -23,12 +35,18 @@ pub mod optypes {
|
|||
pub type O = Dynamic;
|
||||
pub type P = Dynamic;
|
||||
|
||||
/// Insert relocation into code
|
||||
///
|
||||
/// - If integer, just write it to the code
|
||||
/// - Otherwise insert entry into relocation table
|
||||
/// and fill zeroes
|
||||
pub fn insert_reloc(
|
||||
obj: &mut Object,
|
||||
ty: RelocType,
|
||||
val: &Dynamic,
|
||||
) -> Result<(), EvalAltResult> {
|
||||
match () {
|
||||
// Direct references – insert directly to table
|
||||
_ if val.is::<SymbolRef>() => {
|
||||
obj.relocation(RelocKey::Symbol(val.clone_cast::<SymbolRef>().0), ty)
|
||||
}
|
||||
|
@ -38,9 +56,13 @@ pub mod optypes {
|
|||
_ if val.is::<DataRef>() => {
|
||||
obj.relocation(RelocKey::Symbol(val.clone_cast::<DataRef>().symbol.0), ty)
|
||||
}
|
||||
|
||||
// String (indirect) reference
|
||||
_ if val.is_string() => {
|
||||
obj.relocation(RelocKey::Label(val.clone_cast::<ImmutableString>()), ty)
|
||||
}
|
||||
|
||||
// Manual offset
|
||||
_ if val.is_int() => {
|
||||
let int = val.clone_cast::<i64>();
|
||||
match ty {
|
||||
|
@ -49,9 +71,10 @@ pub mod optypes {
|
|||
RelocType::Abs64 => obj.sections.text.extend(int.to_le_bytes()),
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
return Err(EvalAltResult::ErrorMismatchDataType(
|
||||
"SybolRef, UnboundLabel, String or Int".to_owned(),
|
||||
"SymbolRef, UnboundLabel, String or Int".to_owned(),
|
||||
val.type_name().to_owned(),
|
||||
Position::NONE,
|
||||
))
|
||||
|
@ -61,8 +84,23 @@ pub mod optypes {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// 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.
|
||||
macro_rules! gen_insert {
|
||||
(le_bytes: [$($lety:ident),* $(,)?]) => {
|
||||
/// `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);`
|
||||
macro_rules! insert {
|
||||
$(($thing:expr, $obj: expr, $lety) => {
|
||||
$obj.sections.text.extend($thing.to_le_bytes());
|
||||
|
@ -101,6 +139,7 @@ pub mod optypes {
|
|||
use crate::data::DataRef;
|
||||
}
|
||||
|
||||
/// Rhai Types (types for function parameters as Rhai uses only 64bit signed integers)
|
||||
pub mod rity {
|
||||
pub use super::optypes::{A, O, P, R};
|
||||
pub type B = i64;
|
||||
|
@ -109,6 +148,7 @@ pub mod rity {
|
|||
pub type D = i64;
|
||||
}
|
||||
|
||||
/// Generic instruction (instruction of certain operands type) inserts
|
||||
pub mod generic {
|
||||
use {crate::object::Object, rhai::EvalAltResult};
|
||||
|
||||
|
@ -122,28 +162,47 @@ pub mod generic {
|
|||
})
|
||||
}
|
||||
|
||||
/// Generate opcode-generic instruction insert macro
|
||||
macro_rules! gen_ins {
|
||||
($($($name:ident : $ty:ty),*;)*) => {
|
||||
paste::paste! {
|
||||
$(#[inline]
|
||||
pub fn [<$($ty:lower)*>](
|
||||
obj: &mut Object,
|
||||
opcode: u8,
|
||||
$($name: $crate::ins::optypes::$ty),*,
|
||||
) -> Result<(), EvalAltResult> {
|
||||
obj.sections.text.push(opcode);
|
||||
$($crate::ins::optypes::insert!(&$name, obj, $ty);)*
|
||||
Ok(())
|
||||
})*
|
||||
$(
|
||||
/// 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(())
|
||||
}
|
||||
)*
|
||||
|
||||
/// Generate Rhai opcode-specific instruction insertion functions
|
||||
///
|
||||
/// `gen_ins_fn!($obj, $opcode, $optype);` where:
|
||||
/// - `$obj`: Code object
|
||||
/// - `$opcode`: Opcode value
|
||||
macro_rules! gen_ins_fn {
|
||||
$(
|
||||
($obj:expr, $opcode:expr, [<$($ty)*>]) => {
|
||||
// Opcode-specific insertion function
|
||||
// - Parameters = operands
|
||||
move |$($name: $crate::ins::rity::$ty),*| {
|
||||
// Invoke generic function
|
||||
$crate::ins::generic::[<$($ty:lower)*>](
|
||||
&mut *$obj.borrow_mut(),
|
||||
$opcode,
|
||||
$(
|
||||
// Convert to desired type (from Rhai-provided values)
|
||||
$crate::ins::generic::convert_op::<
|
||||
_,
|
||||
$crate::ins::optypes::$ty
|
||||
|
@ -154,11 +213,13 @@ pub mod generic {
|
|||
}
|
||||
};
|
||||
|
||||
// Internal-use: count args
|
||||
(@arg_count [<$($ty)*>]) => {
|
||||
{ ["", $(stringify!($ty)),*].len() - 1 }
|
||||
};
|
||||
)*
|
||||
|
||||
// Specialisation for no-operand instructions
|
||||
($obj:expr, $opcode:expr, N) => {
|
||||
move || {
|
||||
$crate::ins::generic::n(&mut *$obj.borrow_mut(), $opcode);
|
||||
|
@ -166,6 +227,7 @@ pub mod generic {
|
|||
}
|
||||
};
|
||||
|
||||
// Internal-use specialisation: no-operand instructions
|
||||
(@arg_count N) => {
|
||||
{ 0 }
|
||||
};
|
||||
|
@ -174,11 +236,16 @@ pub mod generic {
|
|||
};
|
||||
}
|
||||
|
||||
/// Specialisation for no-operand instructions – simply just push opcode
|
||||
#[inline]
|
||||
pub fn n(obj: &mut Object, opcode: u8) {
|
||||
obj.sections.text.push(opcode);
|
||||
}
|
||||
|
||||
// Generate opcode-generic instruction inserters
|
||||
// (operand identifiers are arbitrary)
|
||||
//
|
||||
// New instruction types have to be added manually here
|
||||
gen_ins! {
|
||||
o0: R, o1: R;
|
||||
o0: R, o1: R, o2: R;
|
||||
|
@ -205,6 +272,17 @@ pub mod generic {
|
|||
pub(super) use gen_ins_fn;
|
||||
}
|
||||
|
||||
/// Generate instructions from instruction table
|
||||
///
|
||||
/// ```ignore
|
||||
/// instructions!(($module, $obj) {
|
||||
/// // Data from instruction table
|
||||
/// $opcode, $mnemonic, $opty, $doc;
|
||||
/// …
|
||||
/// });
|
||||
/// ```
|
||||
/// - `$module`: Rhai module
|
||||
/// - `$obj`: Code object
|
||||
macro_rules! instructions {
|
||||
(
|
||||
($module:expr, $obj:expr $(,)?)
|
||||
|
@ -212,7 +290,10 @@ macro_rules! instructions {
|
|||
) => {{
|
||||
let (module, obj) = ($module, $obj);
|
||||
$({
|
||||
// Object is shared across all functions
|
||||
let obj = Rc::clone(&obj);
|
||||
|
||||
// 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, _>(
|
||||
|
@ -227,7 +308,9 @@ macro_rules! instructions {
|
|||
}};
|
||||
}
|
||||
|
||||
/// Setup instruction insertors
|
||||
pub fn setup(module: &mut Module, obj: Rc<RefCell<Object>>) {
|
||||
// Import instructions table and use it for generation
|
||||
with_builtin_macros::with_builtin! {
|
||||
let $spec = include_from_root!("../hbbytecode/instructions.in") in {
|
||||
instructions!((module, obj) { $spec });
|
||||
|
|
|
@ -1,8 +1,31 @@
|
|||
//! Stuff related to labels
|
||||
|
||||
use {
|
||||
crate::SharedObject,
|
||||
rhai::{Engine, FuncRegistration, ImmutableString, Module},
|
||||
};
|
||||
|
||||
/// Macro for creating functions for Rhai which
|
||||
/// is bit more friendly
|
||||
///
|
||||
/// ```ignore
|
||||
/// shdm_fns!{
|
||||
/// module: $module;
|
||||
/// shared: $shared => $shname;
|
||||
///
|
||||
/// $vis fn $name($param_name: $param_ty, …) -> $ret { … }
|
||||
/// …
|
||||
/// }
|
||||
/// ```
|
||||
/// - `$module`: Rhai module
|
||||
/// - `$shared`: Data to be shared across the functions
|
||||
/// - `$shname`: The binding name inside functions
|
||||
/// - `$vis`: Function visibility for Rhai
|
||||
/// - Lowercased [`rhai::FnNamespace`] variants
|
||||
/// - `$name`: Function name
|
||||
/// - `$param_name`: Parameter name
|
||||
/// - `$param_ty`: Rust parameter type
|
||||
/// - `$ret`: Optional return type (otherwise infer)
|
||||
macro_rules! shdm_fns {
|
||||
(
|
||||
module: $module:expr;
|
||||
|
@ -14,23 +37,26 @@ macro_rules! shdm_fns {
|
|||
) => {{
|
||||
let module = $module;
|
||||
let shared = $shared;
|
||||
$({
|
||||
paste::paste! {
|
||||
$({
|
||||
|
||||
let $shname = SharedObject::clone(&shared);
|
||||
let $shname = SharedObject::clone(&shared);
|
||||
|
||||
FuncRegistration::new(stringify!($name))
|
||||
.with_namespace(rhai::FnNamespace::Global)
|
||||
.set_into_module::<_, { ["", $(stringify!($param_name)),*].len() - 1 }, false, _, true, _>(
|
||||
module,
|
||||
move |$($param_name: $param_ty),*| $(-> $ret)? {
|
||||
let mut $shname = $shname.borrow_mut();
|
||||
$blk
|
||||
}
|
||||
);
|
||||
})*
|
||||
FuncRegistration::new(stringify!($name))
|
||||
.with_namespace(rhai::FnNamespace::[<$vis:camel>])
|
||||
.set_into_module::<_, { ["", $(stringify!($param_name)),*].len() - 1 }, false, _, true, _>(
|
||||
module,
|
||||
move |$($param_name: $param_ty),*| $(-> $ret)? {
|
||||
let mut $shname = $shname.borrow_mut();
|
||||
$blk
|
||||
}
|
||||
);
|
||||
})*
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
/// Label without any place bound
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct UnboundLabel(pub usize);
|
||||
|
||||
|
@ -39,11 +65,13 @@ pub fn setup(engine: &mut Engine, module: &mut Module, object: SharedObject) {
|
|||
module: module;
|
||||
shared: object => obj;
|
||||
|
||||
// Insert unnamed label
|
||||
global fn label() {
|
||||
let symbol = obj.symbol(crate::object::Section::Text);
|
||||
Ok(symbol)
|
||||
}
|
||||
|
||||
// Insert string-labeled label
|
||||
global fn label(label: ImmutableString) {
|
||||
let symbol = obj.symbol(crate::object::Section::Text);
|
||||
obj.labels.insert(label, symbol.0);
|
||||
|
@ -51,6 +79,7 @@ pub fn setup(engine: &mut Engine, module: &mut Module, object: SharedObject) {
|
|||
Ok(symbol)
|
||||
}
|
||||
|
||||
// Declare unbound label (to be bound later)
|
||||
global fn declabel() {
|
||||
let index = obj.symbols.len();
|
||||
obj.symbols.push(None);
|
||||
|
@ -58,6 +87,8 @@ pub fn setup(engine: &mut Engine, module: &mut Module, object: SharedObject) {
|
|||
Ok(UnboundLabel(index))
|
||||
}
|
||||
|
||||
// Declare unbound label (to be bound later)
|
||||
// with string label
|
||||
global fn declabel(label: ImmutableString) {
|
||||
let index = obj.symbols.len();
|
||||
obj.symbols.push(None);
|
||||
|
@ -66,6 +97,7 @@ pub fn setup(engine: &mut Engine, module: &mut Module, object: SharedObject) {
|
|||
Ok(UnboundLabel(index))
|
||||
}
|
||||
|
||||
// Set location for unbound label
|
||||
global fn here(label: UnboundLabel) {
|
||||
obj.symbols[label.0] = Some(crate::object::SymbolEntry {
|
||||
location: crate::object::Section::Text,
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//! Simple flat-bytecode linker
|
||||
|
||||
use {
|
||||
crate::{
|
||||
object::{RelocKey, RelocType, Section},
|
||||
|
@ -8,18 +10,27 @@ use {
|
|||
|
||||
pub fn link(object: SharedObject, out: &mut impl Write) -> std::io::Result<()> {
|
||||
let obj = &mut *object.borrow_mut();
|
||||
|
||||
// Walk relocation table entries
|
||||
for (&loc, entry) in &obj.relocs {
|
||||
let value = match &entry.key {
|
||||
// Symbol – direct reference
|
||||
RelocKey::Symbol(sym) => obj.symbols[*sym],
|
||||
|
||||
// Label – indirect label reference
|
||||
RelocKey::Label(label) => obj.symbols[obj.labels[label]],
|
||||
}
|
||||
.ok_or_else(|| std::io::Error::other("Invalid symbol"))?;
|
||||
|
||||
let offset = match value.location {
|
||||
// Text section is on the beginning
|
||||
Section::Text => value.offset,
|
||||
|
||||
// Data section follows text section immediately
|
||||
Section::Data => value.offset + obj.sections.text.len(),
|
||||
};
|
||||
|
||||
// Insert address or calulate relative offset
|
||||
match entry.ty {
|
||||
RelocType::Rel32 => obj.sections.text[loc..loc + 4]
|
||||
.copy_from_slice(&((offset as isize - loc as isize) as i32).to_le_bytes()),
|
||||
|
@ -30,6 +41,7 @@ pub fn link(object: SharedObject, out: &mut impl Write) -> std::io::Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
// Write to output
|
||||
out.write_all(&obj.sections.text)?;
|
||||
out.write_all(&obj.sections.data)
|
||||
}
|
||||
|
|
|
@ -1,23 +1,31 @@
|
|||
//! Code object
|
||||
|
||||
use {rhai::ImmutableString, std::collections::HashMap};
|
||||
|
||||
/// Section tabel
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Section {
|
||||
Text,
|
||||
Data,
|
||||
}
|
||||
|
||||
/// Symbol entry (in what section, where)
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct SymbolEntry {
|
||||
pub location: Section,
|
||||
pub offset: usize,
|
||||
}
|
||||
|
||||
/// Relocation table key
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum RelocKey {
|
||||
/// Direct reference
|
||||
Symbol(usize),
|
||||
/// Indirect reference
|
||||
Label(ImmutableString),
|
||||
}
|
||||
|
||||
/// Relocation type
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum RelocType {
|
||||
Rel32,
|
||||
|
@ -25,23 +33,30 @@ pub enum RelocType {
|
|||
Abs64,
|
||||
}
|
||||
|
||||
/// Relocation table entry
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RelocEntry {
|
||||
pub key: RelocKey,
|
||||
pub ty: RelocType,
|
||||
}
|
||||
|
||||
/// Object code
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Sections {
|
||||
pub text: Vec<u8>,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Object
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Object {
|
||||
/// Vectors with sections
|
||||
pub sections: Sections,
|
||||
/// Symbol table
|
||||
pub symbols: Vec<Option<SymbolEntry>>,
|
||||
/// Labels to symbols table
|
||||
pub labels: HashMap<ImmutableString, usize>,
|
||||
/// Relocation table
|
||||
pub relocs: HashMap<usize, RelocEntry>,
|
||||
}
|
||||
|
||||
|
@ -50,6 +65,7 @@ pub struct Object {
|
|||
pub struct SymbolRef(pub usize);
|
||||
|
||||
impl Object {
|
||||
/// Insert symbol at current location in specified section
|
||||
pub fn symbol(&mut self, section: Section) -> SymbolRef {
|
||||
let section_buf = match section {
|
||||
Section::Text => &mut self.sections.text,
|
||||
|
@ -64,6 +80,7 @@ impl Object {
|
|||
SymbolRef(self.symbols.len() - 1)
|
||||
}
|
||||
|
||||
/// Insert to relocation table and write zeroes to code
|
||||
pub fn relocation(&mut self, key: RelocKey, ty: RelocType) {
|
||||
self.relocs
|
||||
.insert(self.sections.text.len(), RelocEntry { key, ty });
|
||||
|
|
Loading…
Reference in a new issue