-
Notifications
You must be signed in to change notification settings - Fork 1
Add harm runtime #50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Add harm runtime #50
Changes from 16 commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
e41b68a
runtime: should be renamed to `translator` or `dynasm`
monoid 4dd3f4d
runtime: memory abstraction
monoid af94ee9
`ForeignMemory`
monoid 3f48074
runtime: memory with labels and applied relocations
monoid bff64be
runtime: Assembler::build
monoid 616b0ad
runtime: Fixed memory label manager
monoid f800e54
runtime: no_std
monoid ccca0ec
Review fixes
monoid 1b07e08
Misc
monoid 7b446da
Refactor memory buffer traits
monoid 0dc9bf2
Misc
monoid 25c2c8d
Fix memmap2 trait definitions
monoid 7fc2f45
Recalculate labels
monoid bf3c148
Misc improvements
monoid 102fdc4
runtime generation test
monoid 686ad9a
runtime execution test
monoid e71b8f8
Fix fmt
monoid be073cf
Review fixes
monoid 7f10680
More review fixes
monoid a41c666
Atomic `Memory::try_extend`
monoid File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,155 @@ | ||
| /* Copyright (C) 2026 Ivan Boldyrev | ||
| * | ||
| * This document is licensed under the BSD 3-clause license. | ||
| */ | ||
|
|
||
| use std::collections::HashMap; | ||
|
|
||
| use harm::reloc::{Addr64, LabelId, Offset64, Rel64, Rel64Error}; | ||
|
|
||
| #[derive(Debug, thiserror::Error)] | ||
| pub enum BuilderError { | ||
| #[error("Address overflow: base {0}, offset {1}")] | ||
| AddressOverflow(u64, usize), | ||
| #[error("Relocation error: {nested:?} at offset {offset}")] | ||
| Relocation { nested: Rel64Error, offset: usize }, | ||
| #[error("Undefined label: {0:?}")] | ||
| UndefinedLabel(LabelId), | ||
| } | ||
|
|
||
| /// Do static relocations: recalculate labels and applies relocations, producing memory ready for execution. | ||
| /// | ||
| /// Please note that real memory location may be different from base address: it allows to build at some buffer | ||
| /// and then move data to real position later. | ||
| pub struct Builder<'mem> { | ||
| mem: &'mem mut [u8], // real memory | ||
| base: Addr64, // virtual base, on ARM system usually matches with `mem` start | ||
| } | ||
|
|
||
| impl<'mem> Builder<'mem> { | ||
| pub fn new(mem: &'mem mut [u8], base: Addr64) -> Self { | ||
| Self { mem, base } | ||
| } | ||
|
|
||
| pub fn build( | ||
| self, | ||
| named_labels: impl Iterator<Item = (&'mem str, LabelId)>, | ||
| labels: impl Iterator<Item = (LabelId, Offset64)>, | ||
| relocations: impl Iterator<Item = (usize, Rel64)>, | ||
| ) -> Result<HashMap<String, u64>, BuilderError> { | ||
| // Recalculate labels. | ||
| let labels: HashMap<_, _> = labels | ||
| .map(|(label_id, offset)| { | ||
| // TODO is it wrapping? | ||
| let addr = self.base.wrapping_add_signed(offset); | ||
| (label_id, addr) | ||
| }) | ||
| .collect(); | ||
|
|
||
| // Calculate label addresses. | ||
| let label_addresses = named_labels | ||
| .map(|(name, label_id)| { | ||
| let label_addr = labels | ||
| .get(&label_id) | ||
| .copied() | ||
| .ok_or_else(|| BuilderError::UndefinedLabel(label_id))?; | ||
| Ok((name.to_owned(), label_addr)) | ||
| }) | ||
| .collect::<Result<_, BuilderError>>()?; | ||
|
|
||
| // Apply relocations to the self.mem. | ||
| for (offset, rel) in relocations { | ||
| let label_addr = labels | ||
| .get(&rel.label.id) | ||
| .copied() | ||
| .ok_or_else(|| BuilderError::UndefinedLabel(rel.label.id))?; | ||
| // TODO is it wrapping? | ||
| let label_ref_addr = label_addr.wrapping_add_signed(rel.label.addend); | ||
|
|
||
| rel.apply(self.base, label_ref_addr, self.mem, offset) | ||
| .map_err(|nested| BuilderError::Relocation { nested, offset })?; | ||
| } | ||
|
|
||
| Ok(label_addresses) | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use harm::reloc::{LabelId, LabelRef, Rel64Tag}; | ||
|
|
||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn test_good_offset() { | ||
| let mut mem = vec![0u8; 4]; | ||
| let builder = Builder::new(&mut mem, 0); | ||
| let label_ref = LabelRef { | ||
| id: LabelId(0), | ||
| addend: 0, | ||
| }; | ||
| let relocations = [(0, Rel64::new(Rel64Tag::NONE, label_ref))]; | ||
| let res = builder.build( | ||
| [].into_iter(), | ||
| [(LabelId(0), 4)].into_iter(), | ||
| relocations.into_iter(), | ||
| ); | ||
|
|
||
| assert!(res.is_ok(), "{res:?}"); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_bad_offset() { | ||
| let mut mem = vec![0u8; 4]; | ||
| let builder = Builder::new(&mut mem, 0); | ||
| let label_ref = LabelRef { | ||
| id: LabelId(0), | ||
| addend: 0, | ||
| }; | ||
| // N.B. NONE relocation is 0 bytes wide, so 4 doesn't fail. Use 5. | ||
| let relocations = [(5, Rel64::new(Rel64Tag::NONE, label_ref))]; | ||
| let res = builder.build( | ||
| [].into_iter(), | ||
| [(LabelId(0), 4)].into_iter(), | ||
| relocations.into_iter(), | ||
| ); | ||
|
|
||
| assert!( | ||
| matches!( | ||
| res, | ||
| Err(BuilderError::Relocation { | ||
| nested: _, | ||
| offset: _ | ||
| }) | ||
| ), | ||
| "{res:?}" | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_bad_offset_max() { | ||
| let mut mem = vec![0u8; 4]; | ||
| let builder = Builder::new(&mut mem, 0); | ||
| let label_ref = LabelRef { | ||
| id: LabelId(0), | ||
| addend: 0, | ||
| }; | ||
| let relocations = [(usize::MAX, Rel64::new(Rel64Tag::NONE, label_ref))]; | ||
| let res = builder.build( | ||
| [].into_iter(), | ||
| [(LabelId(0), 4)].into_iter(), | ||
| relocations.into_iter(), | ||
| ); | ||
|
|
||
| assert!( | ||
| matches!( | ||
| res, | ||
| Err(BuilderError::Relocation { | ||
| nested: _, | ||
| offset: _ | ||
| }) | ||
| ), | ||
| "{res:?}" | ||
| ); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,13 @@ | ||
| /* Copyright (C) 2025 Ivan Boldyrev | ||
| /* Copyright (C) 2026 Ivan Boldyrev | ||
| * | ||
| * This document is licensed under the BSD 3-clause license. | ||
| */ | ||
|
|
||
|
|
||
| #[cfg(feature = "alloc")] | ||
| extern crate alloc; | ||
|
|
||
| pub mod builder; | ||
| pub mod labels; | ||
| pub mod memory; | ||
| pub mod runtime; | ||
|
monoid marked this conversation as resolved.
Outdated
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| /* Copyright (C) 2026 Ivan Boldyrev | ||
| * | ||
| * This document is licensed under the BSD 3-clause license. | ||
| */ | ||
|
|
||
| #[cfg(feature = "memmap2")] | ||
| mod memmap2; | ||
|
|
||
| use harm::reloc::Addr64; | ||
|
|
||
| #[cfg(feature = "memmap2")] | ||
| pub use self::memmap2::{MmapBuffer, MmapPositionedMemory}; | ||
|
|
||
| #[cfg(feature = "alloc")] | ||
| pub mod foreign_memory; | ||
| #[cfg(feature = "alloc")] | ||
| pub use self::foreign_memory::ForeignMemoryBuffer; | ||
|
|
||
| pub trait Memory { | ||
| type ExtendError; | ||
|
|
||
| /// Current writing position. | ||
| fn pos(&self) -> usize; | ||
|
|
||
| /// If memory has fixed capacity, return it. | ||
| /// | ||
| /// A `Vec` is not considered a memory of fixed capacity because it can grow indefinitely. | ||
| fn capacity(&self) -> Option<usize>; | ||
|
|
||
| /// Append data to the memory. Should fail when it reaches memory's capacity. | ||
| fn try_extend<I: Iterator<Item = u8>>(&mut self, bytes: I) -> Result<(), Self::ExtendError>; | ||
|
monoid marked this conversation as resolved.
Outdated
|
||
|
|
||
| /// Align position. | ||
| fn align(&mut self, alignment: usize) -> Result<(), Self::ExtendError> { | ||
| let pos = self.pos(); | ||
| let remn = pos % alignment; | ||
| if remn != 0 { | ||
| self.try_extend(core::iter::repeat(0).take(alignment - remn))?; | ||
| } | ||
| Ok(()) | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
| } | ||
| } | ||
|
|
||
| pub trait IntoPositionedMemory<PM> { | ||
| type PositionedMemoryError; | ||
|
|
||
| /// Transform into positioned memory. | ||
| fn into_positioned_memory(self) -> Result<PM, Self::PositionedMemoryError>; | ||
| } | ||
|
|
||
| /// Memory with fixed location that can be transformed to an executable one after relocations are applied. | ||
| pub trait PositionedMemory: AsMut<[u8]> { | ||
| fn get_base_address(&self) -> Addr64; | ||
| } | ||
|
|
||
| pub trait IntoExecutableMemory { | ||
| type ExecutableMemory; | ||
| type ExecutableMemoryError; | ||
|
|
||
| fn into_executable_memory(self) -> Result<Self::ExecutableMemory, Self::ExecutableMemoryError>; | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| #[cfg(feature = "memmap2")] | ||
| #[test] | ||
| fn test_align() { | ||
| use super::*; | ||
|
|
||
| let mut data = &mut Vec::<u8>::new(); | ||
|
|
||
| Memory::align(&mut data, 8); | ||
| assert!(data.is_empty()); | ||
|
|
||
| data.push(1); | ||
| Memory::align(&mut data, 8); | ||
| assert_eq!(data.len(), 8); | ||
|
|
||
| Memory::align(&mut data, 8); | ||
| assert_eq!(data.len(), 8); | ||
|
|
||
| data.extend_from_slice(&[1, 2, 3, 4, 5, 6, 7]); | ||
| Memory::align(&mut data, 8); | ||
| assert_eq!(data.len(), 16); | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.