tests: complete disassembler tests
Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
This commit is contained in:
		
							
								
								
									
										11
									
								
								.github/workflows/main.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								.github/workflows/main.yml
									
									
									
									
										vendored
									
									
								
							@@ -15,14 +15,17 @@ jobs:
 | 
			
		||||
            auto-optimise-store = true
 | 
			
		||||
            experimental-features = nix-command flakes
 | 
			
		||||
 | 
			
		||||
      - name: meson build
 | 
			
		||||
      - name: setup
 | 
			
		||||
        run: nix develop -c meson setup $BUILDDIR
 | 
			
		||||
 | 
			
		||||
      - name: clang-format check
 | 
			
		||||
      - name: fmt
 | 
			
		||||
        run: nix develop -c ninja clang-format-check -C $BUILDDIR
 | 
			
		||||
 | 
			
		||||
      - name: clang-tidy check
 | 
			
		||||
      - name: lint
 | 
			
		||||
        run: nix develop -c ninja clang-tidy -C $BUILDDIR
 | 
			
		||||
 | 
			
		||||
      - name: ninja compile
 | 
			
		||||
      - name: tests
 | 
			
		||||
        run: nix develop -c ninja test -C $BUILDDIR
 | 
			
		||||
 | 
			
		||||
      - name: build
 | 
			
		||||
        run: nix develop -c ninja -C $BUILDDIR
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										22
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
nothing to be seen here yet. LEAVE
 | 
			
		||||
 | 
			
		||||
But if you are curious (probably not), read ahead
 | 
			
		||||
 | 
			
		||||
# Dependencies
 | 
			
		||||
## Tested toolchains
 | 
			
		||||
 | 
			
		||||
- LLVM 16.0.6
 | 
			
		||||
- GCC 12.3.0
 | 
			
		||||
 | 
			
		||||
In theory, any toolchain supporting at least the C++20 standard should work.
 | 
			
		||||
I am using LLVM's clang and libcxx as the primary toolchain.
 | 
			
		||||
 | 
			
		||||
## Static libraries
 | 
			
		||||
 | 
			
		||||
| Name   | Version   | Required? |
 | 
			
		||||
|:------:|:----------|:---------:|
 | 
			
		||||
| fmt    | >= 10.1.1 | yes       |
 | 
			
		||||
| catch2 | >= 3.4    | for tests |
 | 
			
		||||
 | 
			
		||||
This goes without saying but using a different toolchain to compile these libraries before linking probably won't work.
 | 
			
		||||
I will add meson wrap support once LLVM 17 is out, since I want to get rid of fmt.
 | 
			
		||||
							
								
								
									
										8
									
								
								flake.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								flake.lock
									
									
									
										generated
									
									
									
								
							@@ -2,16 +2,16 @@
 | 
			
		||||
  "nodes": {
 | 
			
		||||
    "nixpkgs": {
 | 
			
		||||
      "locked": {
 | 
			
		||||
        "lastModified": 1692007866,
 | 
			
		||||
        "narHash": "sha256-X8w0vPZjZxMm68VCwh/BHDoKRGp+BgzQ6w7Nkif6IVM=",
 | 
			
		||||
        "lastModified": 1694911158,
 | 
			
		||||
        "narHash": "sha256-5WENkcO8O5SuA5pozpVppLGByWfHVv/1wOWgB2+TfV4=",
 | 
			
		||||
        "owner": "nixos",
 | 
			
		||||
        "repo": "nixpkgs",
 | 
			
		||||
        "rev": "de2b8ddf94d6cc6161b7659649594c79bd66c13b",
 | 
			
		||||
        "rev": "46423a1a750594236673c1d741def4e93cf5a8f7",
 | 
			
		||||
        "type": "github"
 | 
			
		||||
      },
 | 
			
		||||
      "original": {
 | 
			
		||||
        "owner": "nixos",
 | 
			
		||||
        "ref": "nixpkgs-unstable",
 | 
			
		||||
        "ref": "master",
 | 
			
		||||
        "repo": "nixpkgs",
 | 
			
		||||
        "type": "github"
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										37
									
								
								flake.nix
									
									
									
									
									
								
							
							
						
						
									
										37
									
								
								flake.nix
									
									
									
									
									
								
							@@ -1,8 +1,10 @@
 | 
			
		||||
{
 | 
			
		||||
  description = "matar";
 | 
			
		||||
 | 
			
		||||
  inputs = {
 | 
			
		||||
    nixpkgs.url = github:nixos/nixpkgs/nixpkgs-unstable;
 | 
			
		||||
    nixpkgs.url = github:nixos/nixpkgs/master;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  outputs = { self, nixpkgs }:
 | 
			
		||||
    let
 | 
			
		||||
      systems = [
 | 
			
		||||
@@ -21,22 +23,30 @@
 | 
			
		||||
        llvm = pkgs.llvmPackages_16;
 | 
			
		||||
        stdenv = llvm.libcxxStdenv;
 | 
			
		||||
 | 
			
		||||
        # packages
 | 
			
		||||
        catch2_v3 = pkgs.callPackage ./nix/catch2.nix { inherit stdenv; };
 | 
			
		||||
 | 
			
		||||
        # TODO: this is ugly
 | 
			
		||||
        #dependencies
 | 
			
		||||
        nativeBuildInputs = with pkgs; [
 | 
			
		||||
          meson
 | 
			
		||||
          ninja
 | 
			
		||||
        nativeBuildInputs = with pkgs;
 | 
			
		||||
          [
 | 
			
		||||
            meson
 | 
			
		||||
            ninja
 | 
			
		||||
 | 
			
		||||
          # libraries
 | 
			
		||||
          pkg-config
 | 
			
		||||
          fmt.dev
 | 
			
		||||
          catch2_v3.dev
 | 
			
		||||
        ];
 | 
			
		||||
            # libraries
 | 
			
		||||
            pkg-config
 | 
			
		||||
            cmake
 | 
			
		||||
 | 
			
		||||
            ((pkgs.fmt.override {
 | 
			
		||||
              inherit stdenv;
 | 
			
		||||
              enableShared = false;
 | 
			
		||||
            }).overrideAttrs (oa: {
 | 
			
		||||
              cmakeFlags = oa.cmakeFlags ++ [ "-DFMT_TEST=off" ];
 | 
			
		||||
            })).dev
 | 
			
		||||
            (catch2_3.override { inherit stdenv; }).out
 | 
			
		||||
          ];
 | 
			
		||||
      in
 | 
			
		||||
      rec {
 | 
			
		||||
        packages = rec {
 | 
			
		||||
          inherit (llvm) libcxxabi;
 | 
			
		||||
          matar = stdenv.mkDerivation rec {
 | 
			
		||||
            name = "matar";
 | 
			
		||||
            version = "0.1";
 | 
			
		||||
@@ -44,6 +54,7 @@
 | 
			
		||||
              ".hh"
 | 
			
		||||
              ".cc"
 | 
			
		||||
              ".build"
 | 
			
		||||
              "meson_options.txt"
 | 
			
		||||
            ];
 | 
			
		||||
            outputs = [ "out" "dev" ];
 | 
			
		||||
 | 
			
		||||
@@ -58,9 +69,7 @@
 | 
			
		||||
          matar = pkgs.mkShell.override { inherit stdenv; } {
 | 
			
		||||
            name = "matar";
 | 
			
		||||
            packages = nativeBuildInputs ++ (with pkgs; [
 | 
			
		||||
              llvm.libcxx
 | 
			
		||||
 | 
			
		||||
              # dev tools
 | 
			
		||||
              # lsp
 | 
			
		||||
              clang-tools_16
 | 
			
		||||
            ]);
 | 
			
		||||
          };
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,7 @@ class Cpu {
 | 
			
		||||
    bool is_flushed;
 | 
			
		||||
 | 
			
		||||
    void chg_mode(const Mode to);
 | 
			
		||||
    void exec_arm(const arm::ArmInstruction instruction);
 | 
			
		||||
    void exec_arm(const arm::Instruction instruction);
 | 
			
		||||
 | 
			
		||||
    struct {
 | 
			
		||||
        std::array<uint32_t, GPR_COUNT - GPR_FIQ_FIRST - 1> fiq;
 | 
			
		||||
 
 | 
			
		||||
@@ -64,7 +64,6 @@ struct HalfwordTransfer {
 | 
			
		||||
    uint8_t rn;
 | 
			
		||||
    bool load;
 | 
			
		||||
    bool write;
 | 
			
		||||
    bool byte;
 | 
			
		||||
    bool imm;
 | 
			
		||||
    bool up;
 | 
			
		||||
    bool pre;
 | 
			
		||||
@@ -152,11 +151,11 @@ using InstructionData = std::variant<BranchAndExchange,
 | 
			
		||||
                                     Undefined,
 | 
			
		||||
                                     SoftwareInterrupt>;
 | 
			
		||||
 | 
			
		||||
struct ArmInstruction {
 | 
			
		||||
struct Instruction {
 | 
			
		||||
    Condition condition;
 | 
			
		||||
    InstructionData data;
 | 
			
		||||
 | 
			
		||||
    ArmInstruction(uint32_t insn);
 | 
			
		||||
    Instruction(uint32_t insn);
 | 
			
		||||
    std::string disassemble();
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										19
									
								
								meson.build
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								meson.build
									
									
									
									
									
								
							@@ -3,21 +3,26 @@ project('matar', 'cpp',
 | 
			
		||||
  license : 'GPLv3',
 | 
			
		||||
  default_options : ['warning_level=3',
 | 
			
		||||
                     'werror=true',
 | 
			
		||||
                     'optimization=3'])
 | 
			
		||||
                     'optimization=3',
 | 
			
		||||
                     'cpp_std=c++20'])
 | 
			
		||||
 | 
			
		||||
compiler = meson.get_compiler('cpp')
 | 
			
		||||
 | 
			
		||||
if compiler.has_argument('-std=c++23')
 | 
			
		||||
'''
 | 
			
		||||
TODO: use <print> and <format> instead of libfmt once LLVM 17 is out
 | 
			
		||||
 | 
			
		||||
if compiler.has_argument('-std=c++2c')
 | 
			
		||||
  add_global_arguments('-std=c++2c', language: 'cpp')
 | 
			
		||||
elif compiler.has_argument('-std=c++23')
 | 
			
		||||
  add_global_arguments('-std=c++23', language: 'cpp')
 | 
			
		||||
elif compiler.has_argument('-std=c++2b')
 | 
			
		||||
  add_global_arguments('-std=c++2b', language: 'cpp')
 | 
			
		||||
elif compiler.has_argument('-std=c++20')
 | 
			
		||||
  add_global_arguments('-std=c++20', language: 'cpp')
 | 
			
		||||
else
 | 
			
		||||
  error(compiler.get_id() + ' ' + compiler.version() + 'does not meet the compiler requirements')
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
'''
 | 
			
		||||
TODO: use <print> and <format> instead of libfmt once LLVM 17 is out
 | 
			
		||||
 | 
			
		||||
if compiler.has_argument('-fexperimental-library')
 | 
			
		||||
  add_global_arguments('-fexperimental-library', language: 'cpp')
 | 
			
		||||
else
 | 
			
		||||
@@ -31,4 +36,6 @@ subdir('include')
 | 
			
		||||
subdir('src')
 | 
			
		||||
subdir('apps')
 | 
			
		||||
 | 
			
		||||
subdir('tests')
 | 
			
		||||
if get_option('tests')
 | 
			
		||||
  subdir('tests')
 | 
			
		||||
endif
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								meson_options.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								meson_options.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
option('tests', type : 'boolean', value : true, description: 'enable tests')
 | 
			
		||||
@@ -1,17 +0,0 @@
 | 
			
		||||
{ stdenv, fetchFromGitHub, meson, ninja }:
 | 
			
		||||
 | 
			
		||||
stdenv.mkDerivation rec {
 | 
			
		||||
  name = "catch2";
 | 
			
		||||
  version = "3.4.0";
 | 
			
		||||
 | 
			
		||||
  src = fetchFromGitHub {
 | 
			
		||||
    owner = "catchorg";
 | 
			
		||||
    repo = "Catch2";
 | 
			
		||||
    rev = "v${version}";
 | 
			
		||||
    sha256 = "sha256-DqGGfNjKPW9HFJrX9arFHyNYjB61uoL6NabZatTWrr0=";
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  nativeBuildInputs = [ meson ninja ];
 | 
			
		||||
 | 
			
		||||
  outputs = [ "out" "dev" ];
 | 
			
		||||
}
 | 
			
		||||
@@ -116,7 +116,7 @@ Cpu::chg_mode(const Mode to) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
Cpu::exec_arm(const arm::ArmInstruction instruction) {
 | 
			
		||||
Cpu::exec_arm(const arm::Instruction instruction) {
 | 
			
		||||
    auto cond = instruction.condition;
 | 
			
		||||
    auto data = instruction.data;
 | 
			
		||||
 | 
			
		||||
@@ -470,8 +470,8 @@ Cpu::exec_arm(const arm::ArmInstruction instruction) {
 | 
			
		||||
 | 
			
		||||
                    if (cpsr.mode() != Mode::User) {
 | 
			
		||||
                        psr.set_all(gpr[data.operand]);
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                    break;
 | 
			
		||||
                case PsrTransfer::Type::Msr_flg:
 | 
			
		||||
                    psr.set_n(get_bit(data.operand, 31));
 | 
			
		||||
                    psr.set_z(get_bit(data.operand, 30));
 | 
			
		||||
@@ -648,7 +648,7 @@ Cpu::exec_arm(const arm::ArmInstruction instruction) {
 | 
			
		||||
            debug(zero);
 | 
			
		||||
            debug(negative);
 | 
			
		||||
 | 
			
		||||
            auto set_conditions = [=]() {
 | 
			
		||||
            auto set_conditions = [this, carry, overflow, negative, zero]() {
 | 
			
		||||
                cpsr.set_c(carry);
 | 
			
		||||
                cpsr.set_v(overflow);
 | 
			
		||||
                cpsr.set_n(negative);
 | 
			
		||||
@@ -693,7 +693,7 @@ Cpu::step() {
 | 
			
		||||
    if (cpsr.state() == State::Arm) {
 | 
			
		||||
        debug(cur_pc);
 | 
			
		||||
        uint32_t x = bus->read_word(cur_pc);
 | 
			
		||||
        arm::ArmInstruction instruction(x);
 | 
			
		||||
        arm::Instruction instruction(x);
 | 
			
		||||
        log_info("{:#034b}", x);
 | 
			
		||||
 | 
			
		||||
        exec_arm(instruction);
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
 | 
			
		||||
using namespace arm;
 | 
			
		||||
 | 
			
		||||
ArmInstruction::ArmInstruction(uint32_t insn)
 | 
			
		||||
Instruction::Instruction(uint32_t insn)
 | 
			
		||||
  : condition(static_cast<Condition>(bit_range(insn, 28, 31))) {
 | 
			
		||||
    // Branch and exhcange
 | 
			
		||||
    if ((insn & 0x0FFFFFF0) == 0x012FFF10) {
 | 
			
		||||
@@ -164,10 +164,9 @@ ArmInstruction::ArmInstruction(uint32_t insn)
 | 
			
		||||
                                .type    = PsrTransfer::Type::Mrs,
 | 
			
		||||
                                .imm     = 0 };
 | 
			
		||||
        } else if ((opcode == OpCode::TEQ || opcode == OpCode::CMN) && !set) {
 | 
			
		||||
            bool imm         = get_bit(insn, 25);
 | 
			
		||||
            uint32_t operand = 0;
 | 
			
		||||
 | 
			
		||||
            if (imm) {
 | 
			
		||||
            if (!imm) {
 | 
			
		||||
                operand = bit_range(insn, 0, 3);
 | 
			
		||||
            } else {
 | 
			
		||||
                uint32_t immediate = bit_range(insn, 0, 7);
 | 
			
		||||
@@ -185,12 +184,11 @@ ArmInstruction::ArmInstruction(uint32_t insn)
 | 
			
		||||
        } else {
 | 
			
		||||
            std::variant<Shift, uint32_t> operand;
 | 
			
		||||
 | 
			
		||||
            if (imm) {
 | 
			
		||||
            if (!imm) {
 | 
			
		||||
                uint32_t immediate = bit_range(insn, 0, 7);
 | 
			
		||||
                uint8_t rotate     = bit_range(insn, 8, 11);
 | 
			
		||||
 | 
			
		||||
                operand = std::rotr(immediate, rotate * 2);
 | 
			
		||||
 | 
			
		||||
            } else {
 | 
			
		||||
                uint8_t rm = bit_range(insn, 0, 3);
 | 
			
		||||
                bool reg   = get_bit(insn, 4);
 | 
			
		||||
@@ -240,7 +238,7 @@ ArmInstruction::ArmInstruction(uint32_t insn)
 | 
			
		||||
 | 
			
		||||
        // Coprocessor data operation
 | 
			
		||||
    } else if ((insn & 0x0F000010) == 0x0E000000) {
 | 
			
		||||
        uint8_t crm    = bit_range(insn, 0, 4);
 | 
			
		||||
        uint8_t crm    = bit_range(insn, 0, 3);
 | 
			
		||||
        uint8_t cp     = bit_range(insn, 5, 7);
 | 
			
		||||
        uint8_t cpn    = bit_range(insn, 8, 11);
 | 
			
		||||
        uint8_t crd    = bit_range(insn, 12, 15);
 | 
			
		||||
@@ -256,7 +254,7 @@ ArmInstruction::ArmInstruction(uint32_t insn)
 | 
			
		||||
 | 
			
		||||
        // Coprocessor register transfer
 | 
			
		||||
    } else if ((insn & 0x0F000010) == 0x0E000010) {
 | 
			
		||||
        uint8_t crm    = bit_range(insn, 0, 4);
 | 
			
		||||
        uint8_t crm    = bit_range(insn, 0, 3);
 | 
			
		||||
        uint8_t cp     = bit_range(insn, 5, 7);
 | 
			
		||||
        uint8_t cpn    = bit_range(insn, 8, 11);
 | 
			
		||||
        uint8_t rd     = bit_range(insn, 12, 15);
 | 
			
		||||
@@ -277,8 +275,7 @@ ArmInstruction::ArmInstruction(uint32_t insn)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string
 | 
			
		||||
ArmInstruction::disassemble() {
 | 
			
		||||
    static const std::string undefined = "UNDEFINED";
 | 
			
		||||
Instruction::disassemble() {
 | 
			
		||||
    // goddamn this is gore
 | 
			
		||||
    // TODO: make this less ugly
 | 
			
		||||
    return std::visit(
 | 
			
		||||
@@ -319,7 +316,7 @@ ArmInstruction::disassemble() {
 | 
			
		||||
                               data.rm,
 | 
			
		||||
                               data.rs);
 | 
			
		||||
        },
 | 
			
		||||
        [](Undefined) { return undefined; },
 | 
			
		||||
        [](Undefined) { return std::string("UND"); },
 | 
			
		||||
        [this](SingleDataSwap& data) {
 | 
			
		||||
            return fmt::format("SWP{}{} R{:d},R{:d},[R{:d}]",
 | 
			
		||||
                               condition,
 | 
			
		||||
@@ -485,7 +482,7 @@ ArmInstruction::disassemble() {
 | 
			
		||||
                               data.cp);
 | 
			
		||||
        },
 | 
			
		||||
        [this](CoprocessorRegisterTransfer& data) {
 | 
			
		||||
            return fmt::format("{}{} p{},{},c{},c{},c{},{}",
 | 
			
		||||
            return fmt::format("{}{} p{},{},R{},c{},c{},{}",
 | 
			
		||||
                               (data.load ? "MRC" : "MCR"),
 | 
			
		||||
                               condition,
 | 
			
		||||
                               data.cpn,
 | 
			
		||||
@@ -495,6 +492,6 @@ ArmInstruction::disassemble() {
 | 
			
		||||
                               data.crm,
 | 
			
		||||
                               data.cp);
 | 
			
		||||
        },
 | 
			
		||||
        [](auto) { return undefined; } },
 | 
			
		||||
        [](auto) { return std::string("unknown instruction"); } },
 | 
			
		||||
      data);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -92,4 +92,6 @@ Psr::condition(Condition cond) const {
 | 
			
		||||
        case Condition::AL:
 | 
			
		||||
            return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -69,6 +69,8 @@ operator<<(std::ostream& os, const OpCode opcode) {
 | 
			
		||||
 | 
			
		||||
uint32_t
 | 
			
		||||
eval_shift(ShiftType shift_type, uint32_t value, uint8_t amount, bool& carry) {
 | 
			
		||||
    uint32_t eval = 0;
 | 
			
		||||
 | 
			
		||||
    switch (shift_type) {
 | 
			
		||||
        case ShiftType::LSL:
 | 
			
		||||
 | 
			
		||||
@@ -77,7 +79,8 @@ eval_shift(ShiftType shift_type, uint32_t value, uint8_t amount, bool& carry) {
 | 
			
		||||
            else if (amount > 32)
 | 
			
		||||
                carry = 0;
 | 
			
		||||
 | 
			
		||||
            return value << amount;
 | 
			
		||||
            eval = value << amount;
 | 
			
		||||
            break;
 | 
			
		||||
        case ShiftType::LSR:
 | 
			
		||||
 | 
			
		||||
            if (amount > 0 && amount <= 32)
 | 
			
		||||
@@ -87,7 +90,8 @@ eval_shift(ShiftType shift_type, uint32_t value, uint8_t amount, bool& carry) {
 | 
			
		||||
            else
 | 
			
		||||
                carry = get_bit(value, 31);
 | 
			
		||||
 | 
			
		||||
            return value >> amount;
 | 
			
		||||
            eval = value >> amount;
 | 
			
		||||
            break;
 | 
			
		||||
        case ShiftType::ASR:
 | 
			
		||||
            if (amount > 0 && amount <= 32)
 | 
			
		||||
                carry = get_bit(value, amount - 1);
 | 
			
		||||
@@ -95,17 +99,21 @@ eval_shift(ShiftType shift_type, uint32_t value, uint8_t amount, bool& carry) {
 | 
			
		||||
                carry = get_bit(value, 31);
 | 
			
		||||
 | 
			
		||||
            return static_cast<int32_t>(value) >> amount;
 | 
			
		||||
            break;
 | 
			
		||||
        case ShiftType::ROR:
 | 
			
		||||
            if (amount == 0) {
 | 
			
		||||
                bool old_carry = carry;
 | 
			
		||||
 | 
			
		||||
                carry = get_bit(value, 0);
 | 
			
		||||
                return (value >> 1) | (old_carry << 31);
 | 
			
		||||
                eval  = (value >> 1) | (old_carry << 31);
 | 
			
		||||
            } else {
 | 
			
		||||
                carry = get_bit(value, (amount % 32 + 31) % 32);
 | 
			
		||||
                return std::rotr(value, amount);
 | 
			
		||||
                eval  = std::rotr(value, amount);
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return eval;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::ostream&
 | 
			
		||||
 
 | 
			
		||||
@@ -58,7 +58,7 @@ Memory::read(size_t address) const {
 | 
			
		||||
        return rom[address - ROM_2_START];
 | 
			
		||||
    } else {
 | 
			
		||||
        log_error("Invalid memory region accessed");
 | 
			
		||||
        return 0;
 | 
			
		||||
        return 0xFF;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,8 +11,7 @@ lib = library(
 | 
			
		||||
  lib_sources,
 | 
			
		||||
  dependencies: [fmt],
 | 
			
		||||
  include_directories: inc,
 | 
			
		||||
  install: true,
 | 
			
		||||
  cpp_args: '-DFMT_HEADER_ONLY'
 | 
			
		||||
  install: true
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
import('pkgconfig').generate(lib)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,15 @@
 | 
			
		||||
#include "cpu/instruction.hh"
 | 
			
		||||
#include "cpu/utility.hh"
 | 
			
		||||
#include <catch2/catch_test_macros.hpp>
 | 
			
		||||
#include <iostream>
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
 | 
			
		||||
static constexpr auto TAG = "disassembler";
 | 
			
		||||
[[maybe_unused]] static constexpr auto TAG = "disassembler";
 | 
			
		||||
 | 
			
		||||
using namespace arm;
 | 
			
		||||
 | 
			
		||||
TEST_CASE("Branch and Exchange", TAG) {
 | 
			
		||||
    uint32_t raw = 0b11000001001011111111111100011010;
 | 
			
		||||
    ArmInstruction instruction(raw);
 | 
			
		||||
    Instruction instruction(raw);
 | 
			
		||||
    BranchAndExchange* bx = nullptr;
 | 
			
		||||
 | 
			
		||||
    REQUIRE((bx = std::get_if<BranchAndExchange>(&instruction.data)));
 | 
			
		||||
@@ -22,7 +22,7 @@ TEST_CASE("Branch and Exchange", TAG) {
 | 
			
		||||
 | 
			
		||||
TEST_CASE("Branch", TAG) {
 | 
			
		||||
    uint32_t raw = 0b11101011100001010111111111000011;
 | 
			
		||||
    ArmInstruction instruction(raw);
 | 
			
		||||
    Instruction instruction(raw);
 | 
			
		||||
    Branch* b = nullptr;
 | 
			
		||||
 | 
			
		||||
    REQUIRE((b = std::get_if<Branch>(&instruction.data)));
 | 
			
		||||
@@ -42,7 +42,7 @@ TEST_CASE("Branch", TAG) {
 | 
			
		||||
 | 
			
		||||
TEST_CASE("Multiply", TAG) {
 | 
			
		||||
    uint32_t raw = 0b00000000001110101110111110010000;
 | 
			
		||||
    ArmInstruction instruction(raw);
 | 
			
		||||
    Instruction instruction(raw);
 | 
			
		||||
    Multiply* mul = nullptr;
 | 
			
		||||
 | 
			
		||||
    REQUIRE((mul = std::get_if<Multiply>(&instruction.data)));
 | 
			
		||||
@@ -64,7 +64,7 @@ TEST_CASE("Multiply", TAG) {
 | 
			
		||||
 | 
			
		||||
TEST_CASE("Multiply Long", TAG) {
 | 
			
		||||
    uint32_t raw = 0b00010000100111100111011010010010;
 | 
			
		||||
    ArmInstruction instruction(raw);
 | 
			
		||||
    Instruction instruction(raw);
 | 
			
		||||
    MultiplyLong* mull = nullptr;
 | 
			
		||||
 | 
			
		||||
    REQUIRE((mull = std::get_if<MultiplyLong>(&instruction.data)));
 | 
			
		||||
@@ -88,9 +88,19 @@ TEST_CASE("Multiply Long", TAG) {
 | 
			
		||||
    REQUIRE(instruction.disassemble() == "UMLALNE R7,R14,R2,R6");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_CASE("Undefined", TAG) {
 | 
			
		||||
    // notice how this is the same as single data transfer except the shift
 | 
			
		||||
    // is now a register based shift
 | 
			
		||||
    uint32_t raw = 0b11100111101000101010111100010110;
 | 
			
		||||
    Instruction instruction(raw);
 | 
			
		||||
 | 
			
		||||
    REQUIRE(instruction.condition == Condition::AL);
 | 
			
		||||
    REQUIRE(instruction.disassemble() == "UND");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_CASE("Single Data Swap", TAG) {
 | 
			
		||||
    uint32_t raw = 0b10100001000010010101000010010110;
 | 
			
		||||
    ArmInstruction instruction(raw);
 | 
			
		||||
    Instruction instruction(raw);
 | 
			
		||||
    SingleDataSwap* swp = nullptr;
 | 
			
		||||
 | 
			
		||||
    REQUIRE((swp = std::get_if<SingleDataSwap>(&instruction.data)));
 | 
			
		||||
@@ -109,7 +119,7 @@ TEST_CASE("Single Data Swap", TAG) {
 | 
			
		||||
 | 
			
		||||
TEST_CASE("Single Data Transfer", TAG) {
 | 
			
		||||
    uint32_t raw = 0b11100111101000101010111100000110;
 | 
			
		||||
    ArmInstruction instruction(raw);
 | 
			
		||||
    Instruction instruction(raw);
 | 
			
		||||
    SingleDataTransfer* ldr = nullptr;
 | 
			
		||||
    Shift* shift            = nullptr;
 | 
			
		||||
 | 
			
		||||
@@ -148,7 +158,7 @@ TEST_CASE("Single Data Transfer", TAG) {
 | 
			
		||||
 | 
			
		||||
TEST_CASE("Halfword Transfer", TAG) {
 | 
			
		||||
    uint32_t raw = 0b00110001101011110010000010110110;
 | 
			
		||||
    ArmInstruction instruction(raw);
 | 
			
		||||
    Instruction instruction(raw);
 | 
			
		||||
    HalfwordTransfer* ldr = nullptr;
 | 
			
		||||
 | 
			
		||||
    REQUIRE((ldr = std::get_if<HalfwordTransfer>(&instruction.data)));
 | 
			
		||||
@@ -179,20 +189,280 @@ TEST_CASE("Halfword Transfer", TAG) {
 | 
			
		||||
    ldr->half = false;
 | 
			
		||||
    REQUIRE(instruction.disassemble() == "LDRCCSB R2,[R15],-R6");
 | 
			
		||||
 | 
			
		||||
    ldr->load = false;
 | 
			
		||||
    // not a register anymore
 | 
			
		||||
    ldr->load   = false;
 | 
			
		||||
    ldr->imm    = 1;
 | 
			
		||||
    ldr->offset = 90;
 | 
			
		||||
    REQUIRE(instruction.disassemble() == "STRCCSB R2,[R15],-#90");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_CASE("Undefined", TAG) {
 | 
			
		||||
    // notice how this is the same as single data transfer except the shift is
 | 
			
		||||
    // now a register based shift
 | 
			
		||||
    uint32_t raw = 0b11100111101000101010111100010110;
 | 
			
		||||
TEST_CASE("Block Data Transfer", TAG) {
 | 
			
		||||
    uint32_t raw = 0b10011001010101110100000101101101;
 | 
			
		||||
    Instruction instruction(raw);
 | 
			
		||||
    BlockDataTransfer* ldm = nullptr;
 | 
			
		||||
 | 
			
		||||
    REQUIRE(ArmInstruction(raw).disassemble() == "UNDEFINED");
 | 
			
		||||
    REQUIRE((ldm = std::get_if<BlockDataTransfer>(&instruction.data)));
 | 
			
		||||
    REQUIRE(instruction.condition == Condition::LS);
 | 
			
		||||
 | 
			
		||||
    raw = 0b11100110000000000000000000010000;
 | 
			
		||||
    REQUIRE(ArmInstruction(raw).disassemble() == "UNDEFINED");
 | 
			
		||||
    {
 | 
			
		||||
        uint16_t regs = 0;
 | 
			
		||||
        regs |= 1 << 0;
 | 
			
		||||
        regs |= 1 << 2;
 | 
			
		||||
        regs |= 1 << 3;
 | 
			
		||||
        regs |= 1 << 5;
 | 
			
		||||
        regs |= 1 << 6;
 | 
			
		||||
        regs |= 1 << 8;
 | 
			
		||||
        regs |= 1 << 14;
 | 
			
		||||
 | 
			
		||||
        REQUIRE(ldm->regs == regs);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    REQUIRE(ldm->rn == 7);
 | 
			
		||||
    REQUIRE(ldm->load == true);
 | 
			
		||||
    REQUIRE(ldm->write == false);
 | 
			
		||||
    REQUIRE(ldm->s == true);
 | 
			
		||||
    REQUIRE(ldm->up == false);
 | 
			
		||||
    REQUIRE(ldm->pre == true);
 | 
			
		||||
 | 
			
		||||
    REQUIRE(instruction.disassemble() == "LDMLSDB R7,{R0,R2,R3,R5,R6,R8,R14}^");
 | 
			
		||||
 | 
			
		||||
    ldm->write = true;
 | 
			
		||||
    ldm->s     = false;
 | 
			
		||||
    ldm->up    = true;
 | 
			
		||||
 | 
			
		||||
    REQUIRE(instruction.disassemble() == "LDMLSIB R7!,{R0,R2,R3,R5,R6,R8,R14}");
 | 
			
		||||
 | 
			
		||||
    ldm->regs &= ~(1 << 6);
 | 
			
		||||
    ldm->regs &= ~(1 << 3);
 | 
			
		||||
    ldm->regs &= ~(1 << 8);
 | 
			
		||||
    ldm->load = false;
 | 
			
		||||
    ldm->pre  = false;
 | 
			
		||||
 | 
			
		||||
    REQUIRE(instruction.disassemble() == "STMLSIA R7!,{R0,R2,R5,R14}");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_CASE("PSR Transfer", TAG) {
 | 
			
		||||
    PsrTransfer* msr = nullptr;
 | 
			
		||||
 | 
			
		||||
    SECTION("MRS") {
 | 
			
		||||
        uint32_t raw = 0b01000001010011111010000000000000;
 | 
			
		||||
        Instruction instruction(raw);
 | 
			
		||||
        PsrTransfer* mrs = nullptr;
 | 
			
		||||
 | 
			
		||||
        REQUIRE((mrs = std::get_if<PsrTransfer>(&instruction.data)));
 | 
			
		||||
        REQUIRE(instruction.condition == Condition::MI);
 | 
			
		||||
 | 
			
		||||
        REQUIRE(mrs->type == PsrTransfer::Type::Mrs);
 | 
			
		||||
        // Operand is a register in the case of MRS (PSR -> Register)
 | 
			
		||||
        REQUIRE(mrs->operand == 10);
 | 
			
		||||
        REQUIRE(mrs->spsr == true);
 | 
			
		||||
 | 
			
		||||
        REQUIRE(instruction.disassemble() == "MRSMI R10,SPSR_all");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    SECTION("MSR") {
 | 
			
		||||
        uint32_t raw = 0b11100001001010011111000000001000;
 | 
			
		||||
        Instruction instruction(raw);
 | 
			
		||||
        PsrTransfer* msr = nullptr;
 | 
			
		||||
 | 
			
		||||
        REQUIRE((msr = std::get_if<PsrTransfer>(&instruction.data)));
 | 
			
		||||
        REQUIRE(instruction.condition == Condition::AL);
 | 
			
		||||
 | 
			
		||||
        REQUIRE(msr->type == PsrTransfer::Type::Msr);
 | 
			
		||||
        // Operand is a register in the case of MSR (Register -> PSR)
 | 
			
		||||
        REQUIRE(msr->operand == 8);
 | 
			
		||||
        REQUIRE(msr->spsr == false);
 | 
			
		||||
 | 
			
		||||
        REQUIRE(instruction.disassemble() == "MSR CPSR_all,R8");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    SECTION("MSR_flg with register operand") {
 | 
			
		||||
        uint32_t raw = 0b01100001001010001111000000001000;
 | 
			
		||||
        Instruction instruction(raw);
 | 
			
		||||
 | 
			
		||||
        REQUIRE((msr = std::get_if<PsrTransfer>(&instruction.data)));
 | 
			
		||||
        REQUIRE(instruction.condition == Condition::VS);
 | 
			
		||||
 | 
			
		||||
        REQUIRE(msr->type == PsrTransfer::Type::Msr_flg);
 | 
			
		||||
        REQUIRE(msr->imm == 0);
 | 
			
		||||
        REQUIRE(msr->operand == 8);
 | 
			
		||||
        REQUIRE(msr->spsr == false);
 | 
			
		||||
 | 
			
		||||
        REQUIRE(instruction.disassemble() == "MSRVS CPSR_flg,R8");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    SECTION("MSR_flg with immediate operand") {
 | 
			
		||||
        uint32_t raw = 0b11100011011010001111011101101000;
 | 
			
		||||
        Instruction instruction(raw);
 | 
			
		||||
 | 
			
		||||
        REQUIRE((msr = std::get_if<PsrTransfer>(&instruction.data)));
 | 
			
		||||
        REQUIRE(instruction.condition == Condition::AL);
 | 
			
		||||
 | 
			
		||||
        REQUIRE(msr->type == PsrTransfer::Type::Msr_flg);
 | 
			
		||||
        REQUIRE(msr->imm == 1);
 | 
			
		||||
 | 
			
		||||
        // 104 (32 bits) rotated by 2 * 7
 | 
			
		||||
        REQUIRE(msr->operand == 27262976);
 | 
			
		||||
        REQUIRE(msr->spsr == true);
 | 
			
		||||
 | 
			
		||||
        REQUIRE(instruction.disassemble() == "MSR SPSR_flg,#27262976");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_CASE("Data Processing", TAG) {
 | 
			
		||||
    uint32_t raw = 0b11100010000111100111101101100001;
 | 
			
		||||
    Instruction instruction(raw);
 | 
			
		||||
    DataProcessing* alu = nullptr;
 | 
			
		||||
    Shift* shift        = nullptr;
 | 
			
		||||
 | 
			
		||||
    REQUIRE((alu = std::get_if<DataProcessing>(&instruction.data)));
 | 
			
		||||
    REQUIRE(instruction.condition == Condition::AL);
 | 
			
		||||
 | 
			
		||||
    // operand 2 is a shifted register
 | 
			
		||||
    REQUIRE((shift = std::get_if<Shift>(&alu->operand)));
 | 
			
		||||
    REQUIRE(shift->rm == 1);
 | 
			
		||||
    REQUIRE(shift->data.immediate == true);
 | 
			
		||||
    REQUIRE(shift->data.type == ShiftType::ROR);
 | 
			
		||||
    REQUIRE(shift->data.operand == 22);
 | 
			
		||||
 | 
			
		||||
    REQUIRE(alu->rd == 7);
 | 
			
		||||
    REQUIRE(alu->rn == 14);
 | 
			
		||||
    REQUIRE(alu->set == true);
 | 
			
		||||
    REQUIRE(alu->opcode == OpCode::AND);
 | 
			
		||||
 | 
			
		||||
    REQUIRE(instruction.disassemble() == "ANDS R7,R14,R1,ROR #22");
 | 
			
		||||
 | 
			
		||||
    shift->data.immediate = false;
 | 
			
		||||
    shift->data.operand   = 2;
 | 
			
		||||
    alu->set              = false;
 | 
			
		||||
 | 
			
		||||
    REQUIRE(instruction.disassemble() == "AND R7,R14,R1,ROR R2");
 | 
			
		||||
 | 
			
		||||
    alu->operand = static_cast<uint32_t>(3300012);
 | 
			
		||||
    REQUIRE(instruction.disassemble() == "AND R7,R14,#3300012");
 | 
			
		||||
 | 
			
		||||
    SECTION("set-only operations") {
 | 
			
		||||
        alu->set = true;
 | 
			
		||||
 | 
			
		||||
        alu->opcode = OpCode::TST;
 | 
			
		||||
        REQUIRE(instruction.disassemble() == "TST R14,#3300012");
 | 
			
		||||
 | 
			
		||||
        alu->opcode = OpCode::TEQ;
 | 
			
		||||
        REQUIRE(instruction.disassemble() == "TEQ R14,#3300012");
 | 
			
		||||
 | 
			
		||||
        alu->opcode = OpCode::CMP;
 | 
			
		||||
        REQUIRE(instruction.disassemble() == "CMP R14,#3300012");
 | 
			
		||||
 | 
			
		||||
        alu->opcode = OpCode::CMN;
 | 
			
		||||
        REQUIRE(instruction.disassemble() == "CMN R14,#3300012");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    SECTION("destination operations") {
 | 
			
		||||
        alu->opcode = OpCode::EOR;
 | 
			
		||||
        REQUIRE(instruction.disassemble() == "EOR R7,R14,#3300012");
 | 
			
		||||
 | 
			
		||||
        alu->opcode = OpCode::SUB;
 | 
			
		||||
        REQUIRE(instruction.disassemble() == "SUB R7,R14,#3300012");
 | 
			
		||||
 | 
			
		||||
        alu->opcode = OpCode::RSB;
 | 
			
		||||
        REQUIRE(instruction.disassemble() == "RSB R7,R14,#3300012");
 | 
			
		||||
 | 
			
		||||
        alu->opcode = OpCode::SUB;
 | 
			
		||||
        REQUIRE(instruction.disassemble() == "SUB R7,R14,#3300012");
 | 
			
		||||
 | 
			
		||||
        alu->opcode = OpCode::ADC;
 | 
			
		||||
        REQUIRE(instruction.disassemble() == "ADC R7,R14,#3300012");
 | 
			
		||||
 | 
			
		||||
        alu->opcode = OpCode::SBC;
 | 
			
		||||
        REQUIRE(instruction.disassemble() == "SBC R7,R14,#3300012");
 | 
			
		||||
 | 
			
		||||
        alu->opcode = OpCode::RSC;
 | 
			
		||||
        REQUIRE(instruction.disassemble() == "RSC R7,R14,#3300012");
 | 
			
		||||
 | 
			
		||||
        alu->opcode = OpCode::ORR;
 | 
			
		||||
        REQUIRE(instruction.disassemble() == "ORR R7,R14,#3300012");
 | 
			
		||||
 | 
			
		||||
        alu->opcode = OpCode::MOV;
 | 
			
		||||
        REQUIRE(instruction.disassemble() == "MOV R7,#3300012");
 | 
			
		||||
 | 
			
		||||
        alu->opcode = OpCode::BIC;
 | 
			
		||||
        REQUIRE(instruction.disassemble() == "BIC R7,R14,#3300012");
 | 
			
		||||
 | 
			
		||||
        alu->opcode = OpCode::MVN;
 | 
			
		||||
        REQUIRE(instruction.disassemble() == "MVN R7,#3300012");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_CASE("Coprocessor Data Transfer", TAG) {
 | 
			
		||||
    uint32_t raw = 0b10101101101001011111000101000110;
 | 
			
		||||
    Instruction instruction(raw);
 | 
			
		||||
    CoprocessorDataTransfer* ldc = nullptr;
 | 
			
		||||
 | 
			
		||||
    REQUIRE((ldc = std::get_if<CoprocessorDataTransfer>(&instruction.data)));
 | 
			
		||||
    REQUIRE(instruction.condition == Condition::GE);
 | 
			
		||||
 | 
			
		||||
    REQUIRE(ldc->offset == 70);
 | 
			
		||||
    REQUIRE(ldc->cpn == 1);
 | 
			
		||||
    REQUIRE(ldc->crd == 15);
 | 
			
		||||
    REQUIRE(ldc->rn == 5);
 | 
			
		||||
    REQUIRE(ldc->load == false);
 | 
			
		||||
    REQUIRE(ldc->write == true);
 | 
			
		||||
    REQUIRE(ldc->len == false);
 | 
			
		||||
    REQUIRE(ldc->up == true);
 | 
			
		||||
    REQUIRE(ldc->pre == true);
 | 
			
		||||
 | 
			
		||||
    REQUIRE(instruction.disassemble() == "STCGE p1,c15,[R5,#70]!");
 | 
			
		||||
 | 
			
		||||
    ldc->load  = true;
 | 
			
		||||
    ldc->pre   = false;
 | 
			
		||||
    ldc->write = false;
 | 
			
		||||
    ldc->len   = true;
 | 
			
		||||
 | 
			
		||||
    REQUIRE(instruction.disassemble() == "LDCGEL p1,c15,[R5],#70");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_CASE("Coprocessor Operand Operation", TAG) {
 | 
			
		||||
    uint32_t raw = 0b11101110101001011111000101000110;
 | 
			
		||||
    Instruction instruction(raw);
 | 
			
		||||
    CoprocessorDataOperation* cdp = nullptr;
 | 
			
		||||
 | 
			
		||||
    REQUIRE((cdp = std::get_if<CoprocessorDataOperation>(&instruction.data)));
 | 
			
		||||
    REQUIRE(instruction.condition == Condition::AL);
 | 
			
		||||
 | 
			
		||||
    REQUIRE(cdp->crm == 6);
 | 
			
		||||
    REQUIRE(cdp->cp == 2);
 | 
			
		||||
    REQUIRE(cdp->cpn == 1);
 | 
			
		||||
    REQUIRE(cdp->crd == 15);
 | 
			
		||||
    REQUIRE(cdp->crn == 5);
 | 
			
		||||
    REQUIRE(cdp->cp_opc == 10);
 | 
			
		||||
 | 
			
		||||
    REQUIRE(instruction.disassemble() == "CDP p1,10,c15,c5,c6,2");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_CASE("Coprocessor Register Transfer", TAG) {
 | 
			
		||||
    uint32_t raw = 0b11101110101001011111000101010110;
 | 
			
		||||
    Instruction instruction(raw);
 | 
			
		||||
    CoprocessorRegisterTransfer* mrc = nullptr;
 | 
			
		||||
 | 
			
		||||
    REQUIRE(
 | 
			
		||||
      (mrc = std::get_if<CoprocessorRegisterTransfer>(&instruction.data)));
 | 
			
		||||
    REQUIRE(instruction.condition == Condition::AL);
 | 
			
		||||
 | 
			
		||||
    REQUIRE(mrc->crm == 6);
 | 
			
		||||
    REQUIRE(mrc->cp == 2);
 | 
			
		||||
    REQUIRE(mrc->cpn == 1);
 | 
			
		||||
    REQUIRE(mrc->rd == 15);
 | 
			
		||||
    REQUIRE(mrc->crn == 5);
 | 
			
		||||
    REQUIRE(mrc->load == false);
 | 
			
		||||
    REQUIRE(mrc->cp_opc == 5);
 | 
			
		||||
 | 
			
		||||
    REQUIRE(instruction.disassemble() == "MCR p1,5,R15,c5,c6,2");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_CASE("Software Interrupt", TAG) {
 | 
			
		||||
    uint32_t raw = 0b00001111101010101010101010101010;
 | 
			
		||||
    Instruction instruction(raw);
 | 
			
		||||
 | 
			
		||||
    REQUIRE(instruction.condition == Condition::EQ);
 | 
			
		||||
    REQUIRE(instruction.disassemble() == "SWIEQ");
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user