Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions common/src/protocol/control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub use embedded_io::ErrorKind;
#[derive(Debug, Serialize, Deserialize)]
pub enum Request {
GetArgs,
CurrentDir,
Exit(i32),
Open(String, FileMode),
Mkdir(String),
Expand All @@ -19,6 +20,7 @@ pub enum Request {
#[derive(Debug, Serialize, Deserialize)]
pub enum Response {
Args(Vec<String>),
CurrentDir(String),
Pipe(PipeData),
Ack,
Err(IoErrorKind),
Expand Down
1 change: 0 additions & 1 deletion fix/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ user = { path = "../user", artifact = "bin", target = "x86_64-unknown-none" }
async-lock = { version = "3.4.1", default-features = false }
bytemuck = "1.24.0"
bitfield-struct = "0.11.0"
blake3 = { version = "1.8.5", default-features = false }
hex = { version = "0.4.3", default-features = false, features = ["alloc"] }
bitint = "0.1.1"
crossbeam-queue = {
Expand Down
1 change: 1 addition & 0 deletions fix/handle/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ edition = "2024"
derive_more = { version = "2.0.1", default-features = false, features = ["full"] }
common = { path = "../../common", default-features = false }
bitint = "0.1.1"
blake3 = { version = "1.8.5", default-features = false }

[features]
testing-mode = []
99 changes: 99 additions & 0 deletions fix/handle/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ use bitint::U5;
pub use common::bitpack::BitPack;
use derive_more::{From, Into, TryUnwrap, Unwrap};

/// Return the storage-independent Fix name of serialized content.
///
/// This does not construct a handle; handle construction stays in Fix's
/// existing Storage/FixOnArca operation path.
pub fn canonicalize(data: &[u8]) -> [u8; 24] {
let hash = blake3::hash(data);
let mut name = [0u8; 24];
name.copy_from_slice(&hash.as_bytes()[..24]);
name
}

const fn bitmask256<const I: u32, const WIDTH: u32>() -> [u8; 32] {
assert!(I + WIDTH <= 256);
let mut out = [0u8; 32];
Expand Down Expand Up @@ -47,6 +58,23 @@ impl Handle {
pub fn is_empty(&self) -> bool {
self.len() == 0
}

pub fn is_literal(&self) -> bool {
matches!(self, Handle::Object(Object::Blob(Blob::Literal(_))))
}

pub fn is_canonical(&self) -> bool {
match self {
Handle::Ref(x) => x.is_canonical(),
Handle::Object(x) => x.is_canonical(),
Handle::Thunk(x) => x.is_canonical(),
Handle::Encode(x) => x.is_canonical(),
}
}

pub fn is_machine(&self) -> bool {
!self.is_canonical()
}
}

#[derive(BitPack, Debug, Copy, Clone, Eq, PartialEq, TryUnwrap, Unwrap, From)]
Expand All @@ -67,6 +95,13 @@ impl Ref {
pub fn is_empty(&self) -> bool {
self.len() == 0
}

fn is_canonical(&self) -> bool {
match self {
Ref::Blob(x) => x.is_canonical(),
Ref::Tree(x) => x.is_canonical(),
}
}
}

#[derive(BitPack, Debug, Copy, Clone, Eq, PartialEq, TryUnwrap, Unwrap, From)]
Expand All @@ -87,6 +122,13 @@ impl Object {
pub fn is_empty(&self) -> bool {
self.len() == 0
}

fn is_canonical(&self) -> bool {
match self {
Object::Blob(x) => x.is_canonical(),
Object::Tree(x) => x.is_canonical(),
}
}
}

#[derive(BitPack, Debug, Copy, Clone, Eq, PartialEq, Unwrap)]
Expand All @@ -108,6 +150,13 @@ impl Thunk {
pub fn is_empty(&self) -> bool {
self.len() == 0
}

fn is_canonical(&self) -> bool {
match self {
Thunk::Identification(x) => x.is_canonical(),
Thunk::Application(x) | Thunk::Selection(x) => x.is_canonical(),
}
}
}

#[derive(BitPack, Debug, Copy, Clone, Eq, PartialEq, TryUnwrap, Unwrap)]
Expand All @@ -128,6 +177,12 @@ impl Encode {
pub fn is_empty(&self) -> bool {
self.len() == 0
}

fn is_canonical(&self) -> bool {
match self {
Encode::Strict(x) | Encode::Shallow(x) => x.is_canonical(),
}
}
}

#[derive(BitPack, Debug, Copy, Clone, Eq, PartialEq, TryUnwrap, Unwrap)]
Expand All @@ -148,6 +203,12 @@ impl Tree {
pub fn is_empty(&self) -> bool {
self.len() == 0
}

fn is_canonical(&self) -> bool {
match self {
Tree::Tree(x) | Tree::Tag(x) => x.is_canonical(),
}
}
}

#[derive(BitPack, Debug, Copy, Clone, Eq, PartialEq, TryUnwrap, Unwrap)]
Expand All @@ -168,6 +229,17 @@ impl Blob {
pub fn is_empty(&self) -> bool {
self.len() == 0
}

pub fn is_literal(&self) -> bool {
matches!(self, Blob::Literal(_))
}

fn is_canonical(&self) -> bool {
match self {
Blob::Blob(x) => x.is_canonical(),
Blob::Literal(_) => true,
}
}
}

#[derive(Debug, Copy, Clone, Eq, PartialEq, From, Into)]
Expand Down Expand Up @@ -200,6 +272,14 @@ impl BlobName {
pub fn is_empty(&self) -> bool {
self.len() == 0
}

pub fn is_canonical(&self) -> bool {
self.0.is_canonical()
}

pub fn is_machine(&self) -> bool {
!self.is_canonical()
}
}

impl LiteralName {
Expand Down Expand Up @@ -243,6 +323,14 @@ impl TreeName {
pub fn is_empty(&self) -> bool {
self.len() == 0
}

pub fn is_canonical(&self) -> bool {
self.0.is_canonical()
}

pub fn is_machine(&self) -> bool {
!self.is_canonical()
}
}

impl common::bitpack::BitPack for BlobName {
Expand Down Expand Up @@ -336,6 +424,13 @@ pub struct RawName {
}

impl RawName {
/// The first high bit left unused by the complete [`Handle`] tag marks a
/// named handle canonical. Zero remains machine-compatible with the
/// original `MemoryStorage`; process-local names do not cross this ABI.
pub const ADDRESS_SHIFT: u32 = Handle::TAGBITS - BlobName::TAGBITS;
pub const MACHINE_NAME: u16 = 0;
pub const CANONICAL_NAME: u16 = 1 << Self::ADDRESS_SHIFT;

pub fn forge(bytes: [u8; 32]) -> Self {
let mut name = [0; 24];
name.copy_from_slice(&bytes[..24]);
Expand All @@ -357,6 +452,10 @@ impl RawName {
bytes[30..32].copy_from_slice(&self.meta.to_le_bytes());
bytes
}

pub fn is_canonical(&self) -> bool {
self.meta & Self::CANONICAL_NAME != 0
}
}

impl From<RawName> for [u8; 32] {
Expand Down
5 changes: 4 additions & 1 deletion fix/src/evaluator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,10 @@ impl<R: Runtime> Evaluator<R> {
.copied()
.map(|x| self.eval(x))
.collect();
self.runtime.storage().add_tree(&evaled)
self.runtime
.storage()
.add_tree(&evaled)
.expect("storage failed to create tree")
}

pub fn eval(&self, handle: Handle) -> Handle {
Expand Down
10 changes: 8 additions & 2 deletions fix/src/interpreter/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,17 @@ impl FixShell for Interpreter<'_> {
type Handle = Handle;

fn create_blob(&self, data: &[u8]) -> Self::Handle {
self.storage.add_blob(data).into()
self.storage
.add_blob(data)
.expect("storage failed to create blob")
.into()
}

fn create_tree(&self, data: &[Self::Handle]) -> Self::Handle {
self.storage.add_tree(data).into()
self.storage
.add_tree(data)
.expect("storage failed to create tree")
.into()
}

fn create_ref(handle: Self::Handle) -> Self::Handle {
Expand Down
68 changes: 57 additions & 11 deletions fix/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@

mod parallel_evaluator;
mod scheduler;
use kernel::host::fs;
use kernel::host::fs::{File, Whence};
use kernel::host::os;
use kernel::prelude::*;

use fix::arca::FixOnArca;
use fix::parser::*;
use fix::runtime::Runtime;
use fix::storage::disk::DiskStorage;
use fix::*;

#[cfg(test)]
Expand All @@ -28,9 +30,13 @@ fn tests() {
fn main() {
let argv = os::argv();

// Subcommand dispatch: `fix init` | `fix eval <file>`.
// Subcommand dispatch: `fix init` | `fix create-blob <file>` | `fix eval <file>`.
match argv.get(1).map(String::as_str) {
Some("init") => init(),
Some("create-blob") => {
let filename = argv.get(2).expect("fix create-blob: expected a file");
create_blob(filename);
}
Some("eval") => {
let path = argv.get(2).expect("fix eval: expected a command file");
eval_file(path)
Expand All @@ -40,24 +46,64 @@ fn main() {
let path = argv.get(2).expect("fix eval: expected a command file");
eval_file_parallel(path);
}
Some(other) => panic!("fix: unknown command '{other}' (expected: init | eval <file> )"),
None => panic!("fix: expected a command (init | eval <file> "),
Some(other) => panic!(
"fix: unknown command '{other}' (expected: init | create-blob <file> | eval <file> | parallel_eval <file>)"
),
None => panic!(
"fix: expected a command (init | create-blob <file> | eval <file> | parallel_eval <file>)"
),
}

kernel::shutdown();
}

/// `fix init`: create the on-disk `.fix` store with its `objects/` and
/// `labels/` subdirs. `mkdir` maps to host `create_dir_all`, so re-running on an
/// existing store is harmless (matches git's "reinitialized existing repository").
/// `fix init`: initialize the on-disk `.fix` store.
fn init() {
for dir in [".fix/objects", ".fix/labels"] {
if let Err(e) = fs::mkdir(dir) {
println!("fix init: failed to create {dir}: {e:?}");
if let Err(error) = DiskStorage::try_new() {
println!("fix init: failed to initialize DiskStorage: {error:?}");
kernel::exit(1);
}
let current_dir = match os::current_dir() {
Ok(path) => path,
Err(error) => {
println!("fix init: cannot resolve the current directory: {error:?}");
kernel::exit(1);
}
};
if current_dir == "/" {
println!("initialized empty fix store in /.fix");
} else {
println!("initialized empty fix store in {current_dir}/.fix");
}
}

/// `fix create-blob <file>`: content-address the file's bytes, persist an out-of-line
/// blob under `.fix/objects`, and print its canonical handle. Small blobs are
/// represented directly by an inline literal handle and require no object file.
fn create_blob(filename: &str) {
let mut file = File::open(filename, true, false, false, false, false)
.unwrap_or_else(|e| panic!("fix create-blob: cannot open {filename}: {e:?}"));
let len = file.seek(Whence::End(0)) as usize;
file.seek(Whence::Start(0));
let mut buf = vec![0; len];
file.read_exact(&mut buf);

let source: FixOnArca = FixOnArca::default();
let machine = match source.storage().add_blob(&buf) {
Ok(blob) => Handle::from(blob),
Err(error) => {
println!("fix create-blob: cannot create machine blob: {error:?}");
kernel::exit(1);
}
};
let destination = DiskStorage;
match destination.import(source.storage(), machine) {
Ok(canonical) => println!("{canonical}"),
Err(error) => {
println!("fix create-blob: export failed: {error:?}");
kernel::exit(1);
}
}
println!("initialized empty fix store in .fix");
}

// Jennifer: tons of redundancy but I just didn't want to change original code,
Expand Down
10 changes: 8 additions & 2 deletions fix/src/parallel_evaluator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,10 @@ impl<R: Runtime> Evaluator<R> {
.copied()
.map(|x| self.eval_test(x, EvalType::Serial))
.collect();
self.runtime.storage().add_tree(&evaled)
self.runtime
.storage()
.add_tree(&evaled)
.expect("storage failed to create tree")
}

fn eval_tree_parallel(&self, handle: Tree) -> Tree {
Expand All @@ -163,7 +166,10 @@ impl<R: Runtime> Evaluator<R> {
for task in tasks {
evaled.push(self.wait_while_helping(&task));
}
self.runtime.storage().add_tree(&evaled)
self.runtime
.storage()
.add_tree(&evaled)
.expect("storage failed to create tree")
}
// elimnate redundancy i think
fn eval_test(&self, handle: Handle, eval_mode: EvalType) -> Handle {
Expand Down
Loading
Loading