diff --git a/Makefile b/Makefile index a0b848b8..70208207 100644 --- a/Makefile +++ b/Makefile @@ -19,6 +19,7 @@ RSS=\ $(SRC)/b.rs \ $(SRC)/ir.rs \ $(SRC)/crust.rs \ + $(SRC)/fighting_consteval.rs \ $(SRC)/flag.rs \ $(SRC)/glob.rs \ $(SRC)/lexer.rs \ diff --git a/src/b.rs b/src/b.rs index 0193e3da..0d759fc4 100644 --- a/src/b.rs +++ b/src/b.rs @@ -30,6 +30,7 @@ pub mod nob; pub mod flag; #[macro_use] pub mod crust; +pub mod fighting_consteval; pub mod arena; pub mod codegen; pub mod lexer; diff --git a/src/bgen.rs b/src/bgen.rs index aa7818a8..b0cb3c39 100644 --- a/src/bgen.rs +++ b/src/bgen.rs @@ -11,6 +11,7 @@ #[macro_use] pub mod crust; pub mod nob; +pub mod fighting_consteval; use core::ffi::*; use core::mem::zeroed; diff --git a/src/btest.rs b/src/btest.rs index e05b5c0b..952bb8a5 100644 --- a/src/btest.rs +++ b/src/btest.rs @@ -6,6 +6,7 @@ #[macro_use] pub mod crust; +pub mod fighting_consteval; #[macro_use] pub mod nob; pub mod targets; @@ -38,7 +39,7 @@ const GARBAGE_FOLDER: *const c_char = c!("./build/tests/"); enum_with_order! { #[derive(Copy, Clone)] - enum TestState in TEST_STATE_ORDER { + enum TestState { Enabled, Disabled, } @@ -53,8 +54,8 @@ impl TestState { } unsafe fn from_name(name: *const c_char) -> Option { - for i in 0..TEST_STATE_ORDER.len() { - let state = (*TEST_STATE_ORDER)[i]; + for i in 0..TestState::VARIANT_COUNT { + let state = (*TestState::ORDER_SLICE)[i]; if strcmp(state.name(), name) == 0 { return Some(state) } @@ -75,7 +76,7 @@ pub enum Outcome { enum_with_order! { #[derive(Copy, Clone)] - enum ReportStatus in REPORT_STATUS_ORDER { + enum ReportStatus { OK, NeverRecorded, StdoutMismatch, @@ -200,7 +201,7 @@ pub unsafe fn usage() { #[derive(Clone, Copy)] pub struct ReportStats { - entries: [usize; REPORT_STATUS_ORDER.len()] + entries: [usize; ReportStatus::ORDER_SLICE.len()] } const RESET: *const c_char = c!("\x1b[0m"); @@ -211,15 +212,15 @@ const RED: *const c_char = c!("\x1b[31m"); const BLUE: *const c_char = c!("\x1b[94m"); pub unsafe fn print_legend(row_width: usize) { - for i in 0..REPORT_STATUS_ORDER.len() { - let status = (*REPORT_STATUS_ORDER)[i]; + for i in 0..ReportStatus::VARIANT_COUNT { + let status = (*ReportStatus::ORDER_SLICE)[i]; printf(c!("%*s%s%s%s - %s\n"), row_width + 2, c!(""), status.color(), status.letter(), RESET, status.description()); } } pub unsafe fn print_report_stats(stats: ReportStats) { - for i in 0..REPORT_STATUS_ORDER.len() { - let status = (*REPORT_STATUS_ORDER)[i]; + for i in 0..ReportStatus::VARIANT_COUNT { + let status = (*ReportStatus::ORDER_SLICE)[i]; printf(c!(" %s%s%s: %-3zu"), status.color(), status.letter(), RESET, stats.entries[i]); } printf(c!("\n")); @@ -652,7 +653,7 @@ pub unsafe fn replay_tests( enum_with_order! { #[derive(Copy, Clone)] - enum Action in ACTION_ORDER { + enum Action { Replay, Record, Prune, @@ -673,8 +674,8 @@ impl Action { } unsafe fn from_name(name: *const c_char) -> Option { - for i in 0..ACTION_ORDER.len() { - let action = (*ACTION_ORDER)[i]; + for i in 0..Action::VARIANT_COUNT { + let action = (*Action::ORDER_SLICE)[i]; if strcmp(name, action.name()) == 0 { return Some(action) } @@ -726,11 +727,11 @@ pub unsafe fn main(argc: i32, argv: *mut*mut c_char) -> Option<()> { if *list_actions { fprintf(stderr(), c!("Available actions:\n")); let mut width = 0; - for i in 0..ACTION_ORDER.len() { - width = cmp::max(width, strlen((*ACTION_ORDER)[i].name())); + for i in 0..Action::VARIANT_COUNT { + width = cmp::max(width, strlen((*Action::ORDER_SLICE)[i].name())); } - for i in 0..ACTION_ORDER.len() { - let action = (*ACTION_ORDER)[i]; + for i in 0..Action::VARIANT_COUNT { + let action = (*Action::ORDER_SLICE)[i]; match action { Action::Replay => { printf(c!(" %-*s - Replay the selected Test Matrix slice with expected outputs from %s.\n"), width, action.name(), json_path); diff --git a/src/codegen/mos6502.rs b/src/codegen/mos6502.rs index e877e88f..9e2bfe56 100644 --- a/src/codegen/mos6502.rs +++ b/src/codegen/mos6502.rs @@ -18,30 +18,10 @@ use crate::arena::{self, Arena}; use crate::targets::TargetAPI; use crate::params::*; -// TODO: does this have to be a macro? -macro_rules! instr_enum { - (enum $n:ident { $($instr:ident),* }) => { - #[derive(Clone, Copy)] - #[repr(u8)] - pub enum $n { - $($instr),*, - COUNT - } - - // TODO: maybe not search linearly, if this is too slow - pub unsafe fn instr_from_string(s: *const c_char) -> Option { - $( - let curr = c!(stringify!($instr)); - if (strcmp(s, curr) == 0) { - return Some($n::$instr); - } - )* - return None; - } - } -} -instr_enum! { +enum_with_order! { + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[repr(u8)] enum Instr { ADC, AND, @@ -70,33 +50,45 @@ instr_enum! { } } -#[derive(Clone, Copy, PartialEq, Eq)] -#[repr(u8)] -pub enum AddrMode { - IMM = 0, - ZP, - ZP_X, - ZP_Y, - ABS, - ABS_X, - ABS_Y, - IND_X, - IND_Y, - - ACC, - REL, - IND, - IMPL, // implied, no arg - - COUNT +// TODO: maybe not search linearly, if this is too slow +pub unsafe fn instr_from_string(s: *const c_char) -> Option { + for i in 0..Instr::VARIANT_COUNT { + let curr: *const c_char = (*Instr::NAMES_SLICE)[i]; + if strcmp(s, curr) == 0 { + return Some((*Instr::ORDER_SLICE)[i]); + } + } + return None; } + +enum_with_order! { + #[derive(Clone, Copy, PartialEq, Eq)] + #[repr(u8)] + enum AddrMode { + IMM = 0, + ZP, + ZP_X, + ZP_Y, + ABS, + ABS_X, + ABS_Y, + IND_X, + IND_Y, + + ACC, + REL, + IND, + IMPL, // implied, no arg + } +} + use Instr::*; use AddrMode::*; // TODO: we currently use 0xFF for invalid opcode, because Some() and None // make this table way too big/hard to read const INVL: u8 = 0xFF; -const OPCODES: [[u8; AddrMode::COUNT as usize]; Instr::COUNT as usize] = +const OPCODES: [[u8; AddrMode::VARIANT_COUNT]; Instr::VARIANT_COUNT] = [// IMM ZP ZP_X ZP_Y, ABS ABS_X ABS_Y IND_X IND_Y ACC REL IND, IMPL /*ADC*/[ 0x69, 0x65, 0x75, INVL, 0x6D, 0x7D, 0x79, 0x61, 0x71, INVL, INVL, INVL, INVL], /*AND*/[ 0x29, 0x25, 0x35, INVL, 0x2D, 0x3D, 0x39, 0x21, 0x31, INVL, INVL, INVL, INVL], diff --git a/src/codegen/uxn/mod.rs b/src/codegen/uxn/mod.rs index 48995ad9..1879d5c1 100644 --- a/src/codegen/uxn/mod.rs +++ b/src/codegen/uxn/mod.rs @@ -126,7 +126,7 @@ pub unsafe fn usage(params: *const [Param]) { enum_with_order! { #[derive(Clone, Copy)] - enum Uxn_Runner in UXN_RUNNER_ORDER { + enum Uxn_Runner { Uxncli, Uxnemu, } @@ -148,8 +148,8 @@ impl Uxn_Runner { } unsafe fn from_name(name: *const c_char) -> Option { - for i in 0..UXN_RUNNER_ORDER.len() { - let runner = (*UXN_RUNNER_ORDER)[i]; + for i in 0..Uxn_Runner::VARIANT_COUNT { + let runner = (*Uxn_Runner::ORDER_SLICE)[i]; if strcmp(runner.name(), name) == 0 { return Some(runner); } @@ -210,8 +210,8 @@ pub unsafe fn new(a: *mut arena::Arena, args: *const [*const c_char]) -> Option< usage(params); log(Log_Level::ERROR, c!("Invalid Uxn runner name `%s`!"), runner_name); log(Log_Level::ERROR, c!("Valid names:")); - for i in 0..UXN_RUNNER_ORDER.len() { - let runner = (*UXN_RUNNER_ORDER)[i]; + for i in 0..Uxn_Runner::VARIANT_COUNT { + let runner = (*Uxn_Runner::ORDER_SLICE)[i]; log(Log_Level::ERROR, c!(" %s - %s"), runner.name(), runner.description()); } return None; diff --git a/src/crust.rs b/src/crust.rs index bd231d16..5de4fd9d 100644 --- a/src/crust.rs +++ b/src/crust.rs @@ -13,20 +13,86 @@ macro_rules! c { #[macro_export] macro_rules! enum_with_order { ( - #[derive($($traits:tt)*)] - enum $name:ident in $order_name:ident { - $($items:tt)* + $( #[$attr:meta $($attr_args:tt)*] )* + enum $Ident:ident { + $( + $Item:ident $(= $init:expr)? + ),* $(,)? } ) => { - #[derive($($traits)*)] - pub enum $name { - $($items)* + $( #[$attr $($attr_args)*] )* + pub enum $Ident { + $($Item $(= $init)?),* } - pub const $order_name: *const [$name] = { - use $name::*; - &[$($items)*] - }; - } + + impl $Ident { + /* + Explanation: + Rust enums have the ability to specify their variants' values, like `A` in this enum: + enum Either { + A = 1, + B, + } + in order to make the ordered slice of variants, we need compile time buffers of unknown sizes. + we take this const fn approach from crates like `const_str`: + we have a const function with a const-generic array width (named order_variants_properly) to have an actual array + on the const stack which we can modify freely and return by-value. this is all it does. + */ + const __ORDER_AND_NAMES_SLICES: (*const [$Ident], *const [*const c_char]) = { + #[allow(unused_imports)] + use $crate::fighting_consteval::*; + #[allow(unused_imports)] + use $crate::c; + // we assert that $Ident must be Copy here (as Crust requires us to do so!) + const fn _assert_copy() {} + const _: () = _assert_copy::<$Ident>(); // your enum must derive Copy to have an ordered slice! + // this is the slice of declarations in declaration order. declaration order does not mean + // order of appearance in the ORDER_SLICE, as rust allows explicit discriminants. + const DECLS_AMOUNT: usize = UNORDERED_DECLS.len(); + const UNORDERED_DECLS: *const [($Ident, *const c_char)] = { + &[ + $( + ($Ident::$Item, c!(stringify!($Item))) + ),* + ] + }; + // this is the slice of declarations that have a specified enum discriminant requirement. + // the order of elements inside this slice doesn't really matter. + // we don't have to worry about clashing requirements as the enum declaration itself handles that for us. + const AMOUNT_SPECIFIED: usize = SPECIFIED_DISCRIMINANTS.len(); + const SPECIFIED_DISCRIMINANTS: *const [($Ident, *const c_char, u128)] = { + &[ + $( $( + ($Ident::$Item, c!(stringify!($Item)), $init), // negative discriminants are not supported, as Self::ORDER_SLICE would need to go backwards. + )? )* + ] + }; + // we pass the unordered declarations and the discriminant requirements to `order_decls_properly`, + // which handles the discriminant resolution in a const fn that is fully evaluated at const. + const RES: ([$Ident; DECLS_AMOUNT], [*const c_char; DECLS_AMOUNT]) = unsafe { + match const { order_decls_properly::<$Ident, DECLS_AMOUNT, AMOUNT_SPECIFIED>( + &*mkarray::<_, DECLS_AMOUNT>(UNORDERED_DECLS), + &*mkarray::<_, AMOUNT_SPECIFIED>(SPECIFIED_DISCRIMINANTS) + ) } { + Ok(v) => v, + Err(OrderDeclsError::RanOutOfDecls) => + panic!("enum_with_order: failed to order enum variants properly.\n\tthis is likely due to discriminant requirements leaving holes in the resulting ORDER_SLICE, which is not supported"), + Err(OrderDeclsError::FinalSliceMissingEntries) => + panic!("enum_with_order: critical sanity check failed at compile time. this is a bug.\n\tthere were entries in your declaration that did not end up in the resulting `Self::ORDER_SLICE`"), + } + }; + // as constants don't allow destructuring (as in `const (ORDER, NAMES) = ...;`), we unpack RES + // manually and "return" it as the constant. + ( + &RES.0, + &RES.1 + ) + }; + pub const ORDER_SLICE: *const [$Ident] = $Ident::__ORDER_AND_NAMES_SLICES.0; + pub const NAMES_SLICE: *const [*const c_char] = $Ident::__ORDER_AND_NAMES_SLICES.1; + pub const VARIANT_COUNT: usize = unsafe { (&*$Ident::ORDER_SLICE).len() }; + } + }; } pub unsafe fn slice_contains(slice: *const [Value], needle: *const Value) -> bool { diff --git a/src/fighting_consteval.rs b/src/fighting_consteval.rs new file mode 100644 index 00000000..754eb1c2 --- /dev/null +++ b/src/fighting_consteval.rs @@ -0,0 +1,226 @@ +use core::mem::MaybeUninit; +use core::mem::size_of; +use core::ffi::*; + +#[track_caller] +pub const unsafe fn mkarray(slice: *const [T]) -> *const [T; N] { + if slice.len() != N { + panic!("slice length does not match array length"); + } + &*(slice as *const [T; N]) +} + +pub const unsafe fn mkslice(array: *const [T; N]) -> *const [T] { + core::ptr::slice_from_raw_parts(array.cast::(), N) +} + +pub const unsafe fn bitwise_partialeq(lhs: *const T, rhs: *const T) -> bool { + let lhs_bytes = lhs as *const u8; + let rhs_bytes = rhs as *const u8; + let size = size_of::(); + let mut i = 0; + while i < size { + if *lhs_bytes.add(i) != *rhs_bytes.add(i) { + return false; + } + i += 1; + } + true +} + +pub const unsafe fn const_slice_bitwise_contains( + haystack: *const [T], + needle: *const T, +) -> bool { + let mut i = 0; + while i < haystack.len() { + let indexed = slice_index(haystack, i); + if bitwise_partialeq(indexed, needle) { + return true; + } + i += 1; + } + false +} + +#[track_caller] +pub const unsafe fn slice_index(slice: *const [T], index: usize) -> *const T { + assert!(index < slice.len(), "slice index out of bounds"); + slice.cast::().add(index) +} + +#[track_caller] +pub const unsafe fn slice_index_mut(slice: *mut [T], index: usize) -> *mut T { + assert!(index < slice.len(), "slice index out of bounds"); + slice.cast::().add(index) +} + +pub const unsafe fn reduce_slice_to_array_of_field_copied< + T, + const LEN_SPECIFIED: usize +>( + slice: *const [(T, *const c_char, u128)] +) -> [T; LEN_SPECIFIED] { + let mut buf: [MaybeUninit; LEN_SPECIFIED] = + [const { MaybeUninit::uninit() }; LEN_SPECIFIED]; + let mut i = 0; + while i < LEN_SPECIFIED { + let indexed = slice_index(slice, i); + buf[i].write((&raw const (*indexed).0).read()); + i += 1; + } + buf.as_ptr().cast::<[T; LEN_SPECIFIED]>().read() +} + +pub const unsafe fn get_unspecified_from_unordered_and_specified_decls< + T, + const LEN_TOTAL: usize, + const LEN_SPECIFIED: usize +>( + total_unordered: *const [(T, *const c_char ); LEN_TOTAL ], + specified : *const [(T, *const c_char, u128); LEN_SPECIFIED], +) -> Result<( + [MaybeUninit<(T, *const c_char)>; LEN_TOTAL], + usize +), OrderDeclsError> { + // N.B. Uninit([ T ]) and not [ Uninit(T) ] + // we use Uninit as a replacement for ManuallyDrop, so we can let the const evaluator + // rest assured knowing that whether T: Drop or not is irrelevant. + // the arrays are always in a fully initialized state. + let specified_array: MaybeUninit<[T ; LEN_SPECIFIED]> = MaybeUninit::new(reduce_slice_to_array_of_field_copied(specified)); + let mut unspecified: MaybeUninit<[Option<(T, *const c_char)>; LEN_TOTAL ]> = MaybeUninit::new([const { None }; LEN_TOTAL]); + let mut unspecified_len = 0; + let mut i = 0; + + while i < LEN_TOTAL { + let in_question = slice_index(total_unordered, i); + if !const_slice_bitwise_contains( + mkslice::(specified_array.as_ptr()), + &raw const (*in_question).0 + ) { + core::ptr::write( + slice_index_mut(unspecified.as_mut_ptr(), unspecified_len), + Some(in_question.read()) + ); + unspecified_len += 1; + } + i += 1; + } + + let mut final_buf: [MaybeUninit<(T, *const c_char)>; LEN_TOTAL] = [const { MaybeUninit::uninit() }; LEN_TOTAL]; + let mut j = 0; + while j < unspecified_len { + let val = slice_index(unspecified.as_ptr(), j); + match &*val { + &Some(ref t) => + final_buf[j].write((&raw const *t).read()), + &None => return Err(OrderDeclsError::RanOutOfDecls), + }; + j += 1; + } + + Ok((final_buf, unspecified_len)) +} + +#[derive(Clone, Copy)] +pub enum OrderDeclsError { + RanOutOfDecls, + FinalSliceMissingEntries, +} + +pub const unsafe fn order_decls_properly( + total_unordered: *const [(T, *const c_char ); LEN_TOTAL ], + specified: *const [(T, *const c_char, u128); LEN_SPECIFIED], +) -> Result<( + [T ; LEN_TOTAL], + [*const c_char; LEN_TOTAL] +), OrderDeclsError> { + // [D, D, D, D, D, D, D, D, D, D, D ] total_unordered + // [D=5, D=3 ] specified + // [tu0, tu1, tu2, sp1, tu3, sp0, tu4, tu5, tu6, tu7, tu8 ] result + let res = match get_unspecified_from_unordered_and_specified_decls::< + T, LEN_TOTAL, LEN_SPECIFIED + >( + total_unordered, specified + ) { + Ok(t) => t, + Err(e) => return Err(e), + }; + + // N.B. [ Uninit(T) ] and not Uninit([ T ]) + let unspecified: [MaybeUninit<(T, *const c_char)>; LEN_TOTAL] = res.0; + let unspecified_len: usize = res.1; + // N.B. [ Uninit(T) ] and not Uninit([ T ]) + // these are incrementally initialized arrays. we will be returning these from the function. + let mut result_t : [MaybeUninit ; LEN_TOTAL] = [const { MaybeUninit::uninit() }; LEN_TOTAL]; + let mut result_char: [MaybeUninit<*const c_char> ; LEN_TOTAL] = [const { MaybeUninit::uninit() }; LEN_TOTAL]; + let mut unspecified_iter = 0; + let mut i = 0; + while i < LEN_TOTAL { + if let Some(found) = 'a: { + // specified.iter().find(|(_, _, discrim)| *discrim == i as u128) + let mut j = 0; + while j < LEN_SPECIFIED { + let x = slice_index(specified, j); + if *&raw const (*x).2 == i as u128 { + break 'a Some(x); + } + j += 1; + } + break 'a None; + } { + // result.push((found.0, found.1.clone())); + result_t[i].write((&raw const (*found).0).read()); + result_char[i].write((&raw const (*found).1).read()); + } else if let Some(unsp) = { + // unspecified_iter.next() + if unspecified_iter < unspecified_len { + let val = unspecified.as_ptr().add(unspecified_iter); + unspecified_iter += 1; + Some(val) + } else { + None + } + } { + // result.push(unsp.clone()); + result_t[i].write((&raw const (*(*unsp).as_ptr()).0).read()); + result_char[i].write((&raw const (*(*unsp).as_ptr()).1).read()); + } else { + return Err(OrderDeclsError::RanOutOfDecls); + } + i += 1; + } + + if !'all_entries_from_total_exist_in_result: { + let mut j = 0; + while j < LEN_TOTAL { + let ptotal = slice_index(total_unordered, j); + let mut k = 0; + let mut found = false; + while k < LEN_TOTAL { + let pcanditate = slice_index(&result_t, k).cast::(); + if bitwise_partialeq(pcanditate, &raw const (*ptotal).0) { + if found == true { + panic!("duplicate entry found in ordered slice."); + } + found = true; + + } + k += 1; + } + if !found { + break 'all_entries_from_total_exist_in_result false; + } + j += 1; + } + break 'all_entries_from_total_exist_in_result true; + } { + return Err(OrderDeclsError::FinalSliceMissingEntries); + } + + Ok(( + result_t.as_ptr().cast::<[T; LEN_TOTAL]>().read(), + result_char.as_ptr().cast::<[*const c_char; LEN_TOTAL]>().read() + )) +} + diff --git a/src/nob.rs b/src/nob.rs index 2b2d2a76..4a15eb8c 100644 --- a/src/nob.rs +++ b/src/nob.rs @@ -122,7 +122,7 @@ pub enum Log_Level { enum_with_order! { #[derive(Clone, Copy)] - enum File_Type in FILE_TYPE_ORDER { + enum File_Type { REGULAR, DIRECTORY, SYMLINK, @@ -176,7 +176,7 @@ pub unsafe fn get_file_type(path: *const c_char) -> Option { } let result = get_file_type_raw(path); if result < 0 { return None; } - Some((*FILE_TYPE_ORDER)[result as usize]) + Some((*File_Type::ORDER_SLICE)[result as usize]) } pub unsafe fn write_entire_file(path: *const c_char, data: *const c_void, size: usize) -> Option<()> {