charm 0.0.1

ARM assembler & disassembler generated from the ARM exploration tools.
Documentation
#[cfg(test)]
mod tests {
    use crate::*;
    use charm::core::a64::config::*;
    use charm::core::a64::decoder::*;
    use charm::core::a64::encoder::*;
    use charm::core::a64::formatter::*;

    #[test]
    fn test_csinv_32_condsel() {
        // Checks if the values of each field corresponds to the ones expected by
        // the instruction currently being tested. This allows us to detect and
        // skip generated values that would make the resulting encoding correspond
        // to another instruction.
        #[allow(non_snake_case, unused)] 
        fn insn_fields_check(sf: u32, op: u32, S: u32, field_28: u32, field_11: u32, o2: u32, Rm: u32, cond: u32, Rn: u32, Rd: u32) -> bool {
            if !(sf == 0b0) {
                return false;
            }
            if !(op == 0b1) {
                return false;
            }
            if !(S == 0b0) {
                return false;
            }
            if !(field_28 == 0b11010100) {
                return false;
            }
            if !(field_11 == 0b0) {
                return false;
            }
            if !(o2 == 0b0) {
                return false;
            }
            return true;
        }

        let llvm_disassembler = llvm_create_disassembler(
            "aarch64",
            "cortex-a725",
            "+v9.6a,+cssc,+mte,+cpa,+pauth,+pauth-lr,+gcs,+ls64,+rcpc3,+lse128,+d128,+the",
        ).unwrap();

        // The instruction encoding with only fixed values.
        let insn_base = 0b01011010100000000000000000000000u32;

        // Bitwidths of variable fields used in the encoding.
        let operand_values_bitwidths = [5, 4, 5, 5];

        // The iterator that will generate values for each variable fields.
        let operand_values_iter = TestIterator::new(&operand_values_bitwidths);

        // The number of instructions Charm was able to decode.
        let mut charm_decoded_insn_count = 0;
        // The number of instructions LLVM was able to decode.
        let mut llvm_decoded_insn_count = 0;

        for operand_values in operand_values_iter {
            // Make sure the generated values correspond to the current instruction.
            if !insn_fields_check(
                0,
                1,
                0,
                212,
                0,
                0,
                operand_values[0] & 31,
                operand_values[1] & 15,
                operand_values[2] & 31,
                operand_values[3] & 31,
            ) {
                continue;
            }

            // We use the test values to create the instruction encoding.
            let mut initial_insn = insn_base;
            initial_insn |= (operand_values[0] & 31) << 16;
            initial_insn |= (operand_values[1] & 15) << 12;
            initial_insn |= (operand_values[2] & 31) << 5;
            initial_insn |= (operand_values[3] & 31) << 0;
            let mut initial_insn_bytes = initial_insn.to_le_bytes();

            // The instruction is first decoded using LLVM.
            let llvm_insn_array = llvm_disassemble(llvm_disassembler, &mut initial_insn_bytes, 0);

            // If LLVM returns an error, we assume the instruction is invalid and
            // simply return.
            if llvm_insn_array.is_empty() {
                // TODO: add a check to ensure Charm also fails to decode this
                // instruction once the ARM pseudocode interpreter has been implemented.
                continue;
            }
            llvm_decoded_insn_count += 1;

            // LLVM returns an array of string, since we disassembled one instruction
            // we only need the first one.
            let llvm_insn_formatted = llvm_insn_array[0].clone();
            let llvm_insn_formatted = llvm_insn_formatted
                .split_once(" /")
                .unwrap_or((llvm_insn_formatted.as_str(), ""))
                .0.to_owned();

            // We instanciate a Charm encoder/decoder/formatter with an LLVM config to
            // make the output match LLVM's.
            let config = ConfigLLVM::new();
            let mut decoder = Decoder::new(&initial_insn_bytes, config);
            let mut encoder = Encoder::new();
            let mut formatter = Fmt {};
            let mut charm_insn_formatted = String::new();

            // The instruction is decoded and then re-encoded, before being formatted
            // into a string.
            let charm_insn = decoder.decode().unwrap();
            let charm_insn_size = encoder.encode(&charm_insn).unwrap();
            assert_eq!(charm_insn_size, 4);
            let charm_insn_bytes = encoder.take();
            formatter.format(&mut charm_insn_formatted, &config, &charm_insn).unwrap();

            charm_decoded_insn_count += 1;

            // We make sure both the encoded and formatted instruction correspond to
            // LLVM's output.
            assert_eq!(charm_insn_formatted, llvm_insn_formatted);
            assert_eq!(charm_insn_bytes, initial_insn_bytes);
        }

        // Make sure that at least one instrution was decoded, and that LLVM and
        // Charm decoded the same amount of instructions.
        assert!(
            llvm_decoded_insn_count != 0 
            || charm_decoded_insn_count == llvm_decoded_insn_count
        );
    }

    #[test]
    fn test_csinv_64_condsel() {
        // Checks if the values of each field corresponds to the ones expected by
        // the instruction currently being tested. This allows us to detect and
        // skip generated values that would make the resulting encoding correspond
        // to another instruction.
        #[allow(non_snake_case, unused)] 
        fn insn_fields_check(sf: u32, op: u32, S: u32, field_28: u32, field_11: u32, o2: u32, Rm: u32, cond: u32, Rn: u32, Rd: u32) -> bool {
            if !(sf == 0b1) {
                return false;
            }
            if !(op == 0b1) {
                return false;
            }
            if !(S == 0b0) {
                return false;
            }
            if !(field_28 == 0b11010100) {
                return false;
            }
            if !(field_11 == 0b0) {
                return false;
            }
            if !(o2 == 0b0) {
                return false;
            }
            return true;
        }

        let llvm_disassembler = llvm_create_disassembler(
            "aarch64",
            "cortex-a725",
            "+v9.6a,+cssc,+mte,+cpa,+pauth,+pauth-lr,+gcs,+ls64,+rcpc3,+lse128,+d128,+the",
        ).unwrap();

        // The instruction encoding with only fixed values.
        let insn_base = 0b11011010100000000000000000000000u32;

        // Bitwidths of variable fields used in the encoding.
        let operand_values_bitwidths = [5, 4, 5, 5];

        // The iterator that will generate values for each variable fields.
        let operand_values_iter = TestIterator::new(&operand_values_bitwidths);

        // The number of instructions Charm was able to decode.
        let mut charm_decoded_insn_count = 0;
        // The number of instructions LLVM was able to decode.
        let mut llvm_decoded_insn_count = 0;

        for operand_values in operand_values_iter {
            // Make sure the generated values correspond to the current instruction.
            if !insn_fields_check(
                1,
                1,
                0,
                212,
                0,
                0,
                operand_values[0] & 31,
                operand_values[1] & 15,
                operand_values[2] & 31,
                operand_values[3] & 31,
            ) {
                continue;
            }

            // We use the test values to create the instruction encoding.
            let mut initial_insn = insn_base;
            initial_insn |= (operand_values[0] & 31) << 16;
            initial_insn |= (operand_values[1] & 15) << 12;
            initial_insn |= (operand_values[2] & 31) << 5;
            initial_insn |= (operand_values[3] & 31) << 0;
            let mut initial_insn_bytes = initial_insn.to_le_bytes();

            // The instruction is first decoded using LLVM.
            let llvm_insn_array = llvm_disassemble(llvm_disassembler, &mut initial_insn_bytes, 0);

            // If LLVM returns an error, we assume the instruction is invalid and
            // simply return.
            if llvm_insn_array.is_empty() {
                // TODO: add a check to ensure Charm also fails to decode this
                // instruction once the ARM pseudocode interpreter has been implemented.
                continue;
            }
            llvm_decoded_insn_count += 1;

            // LLVM returns an array of string, since we disassembled one instruction
            // we only need the first one.
            let llvm_insn_formatted = llvm_insn_array[0].clone();
            let llvm_insn_formatted = llvm_insn_formatted
                .split_once(" /")
                .unwrap_or((llvm_insn_formatted.as_str(), ""))
                .0.to_owned();

            // We instanciate a Charm encoder/decoder/formatter with an LLVM config to
            // make the output match LLVM's.
            let config = ConfigLLVM::new();
            let mut decoder = Decoder::new(&initial_insn_bytes, config);
            let mut encoder = Encoder::new();
            let mut formatter = Fmt {};
            let mut charm_insn_formatted = String::new();

            // The instruction is decoded and then re-encoded, before being formatted
            // into a string.
            let charm_insn = decoder.decode().unwrap();
            let charm_insn_size = encoder.encode(&charm_insn).unwrap();
            assert_eq!(charm_insn_size, 4);
            let charm_insn_bytes = encoder.take();
            formatter.format(&mut charm_insn_formatted, &config, &charm_insn).unwrap();

            charm_decoded_insn_count += 1;

            // We make sure both the encoded and formatted instruction correspond to
            // LLVM's output.
            assert_eq!(charm_insn_formatted, llvm_insn_formatted);
            assert_eq!(charm_insn_bytes, initial_insn_bytes);
        }

        // Make sure that at least one instrution was decoded, and that LLVM and
        // Charm decoded the same amount of instructions.
        assert!(
            llvm_decoded_insn_count != 0 
            || charm_decoded_insn_count == llvm_decoded_insn_count
        );
    }
}