LCOV - code coverage report
Current view: top level - src - virtual_cpu.cpp Hit Total Coverage
Test: Malbolge Unit Test Code Coverage Lines: 249 252 98.8 %
Date: 2021-02-03 17:18:54
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* Cam Mannett 2020
       2             :  *
       3             :  * See LICENSE file
       4             :  */
       5             : 
       6             : #include "malbolge/virtual_cpu.hpp"
       7             : #include "malbolge/cpu_instruction.hpp"
       8             : #include "malbolge/log.hpp"
       9             : 
      10             : #include <boost/asio/io_context.hpp>
      11             : #include <boost/asio/post.hpp>
      12             : 
      13             : #include <thread>
      14             : #include <deque>
      15             : #include <unordered_map>
      16             : #include <atomic>
      17             : 
      18             : using namespace malbolge;
      19             : 
      20             : class virtual_cpu::impl_t : public std::enable_shared_from_this<impl_t>
      21             : {
      22             : public:
      23             :     class breakpoint
      24             :     {
      25             :     public:
      26          13 :         breakpoint(math::ternary a, std::size_t i = 0) :
      27             :             address_{a},
      28             :             ignore_count_{i},
      29          13 :             pre_{false}
      30          13 :         {}
      31             : 
      32             :         [[nodiscard]]
      33          56 :         bool operator()() noexcept
      34             :         {
      35          56 :             if (pre_) {
      36          10 :                 pre_ = false;
      37          10 :                 return false;
      38             :             }
      39             : 
      40          46 :             if (ignore_count_ == 0) {
      41          12 :                 pre_ = true;
      42          12 :                 return true;
      43             :             }
      44             : 
      45          34 :             --ignore_count_;
      46          34 :             return false;
      47             :         }
      48             : 
      49             :     private:
      50             :         math::ternary address_;
      51             :         std::size_t ignore_count_;
      52             : 
      53             :         // The breakpoint is fired (and this variable set) on entry to an
      54             :         // address, so on the next run() call the breakpoint will be hit again -
      55             :         // this variable being set high will cause the breakpoint to be ignored.
      56             :         // This must be set low after, otherwise the breakpoint will never fire
      57             :         // again
      58             :         bool pre_;
      59             :     };
      60             : 
      61             :     class input
      62             :     {
      63             :     public:
      64           9 :         input(std::string p) noexcept :
      65           9 :             phrase_{std::move(p)},
      66           9 :             view_{phrase_.data()}
      67           9 :         {}
      68             : 
      69             :         [[nodiscard]]
      70          65 :         char get() noexcept
      71             :         {
      72          65 :             if (view_.empty()) {
      73           9 :                 return 0;
      74             :             }
      75             : 
      76          56 :             auto c = view_.front();
      77          56 :             view_.remove_prefix(1);
      78          56 :             return c;
      79             :         }
      80             : 
      81             :     private:
      82             :         std::string phrase_;
      83             :         std::string_view view_;
      84             :     };
      85             : 
      86          19 :     explicit impl_t(virtual_memory vm) :
      87          38 :         worker_guard_{ctx.get_executor()},
      88          19 :         vmem(std::move(vm)),
      89          19 :         c{vmem.begin()},
      90          19 :         d{vmem.begin()},
      91             :         p_counter{0},
      92          38 :         state_{virtual_cpu::execution_state::READY}
      93          19 :     {}
      94             : 
      95             :     [[nodiscard]]
      96          88 :     virtual_cpu::execution_state state() const noexcept
      97             :     {
      98          88 :         return state_;
      99             :     }
     100             : 
     101          80 :     void set_state(virtual_cpu::execution_state new_state,
     102             :                    std::exception_ptr eptr = {})
     103             :     {
     104          80 :         if (state_ == new_state) {
     105           8 :             return;
     106             :         }
     107             : 
     108          72 :         state_ = new_state;
     109          72 :         state_sig(state_, eptr);
     110             :     }
     111             : 
     112          13 :     void stop()
     113             :     {
     114          13 :         worker_guard_.reset();
     115          13 :         ctx.stop();
     116          13 :         if (thread.joinable()) {
     117          13 :             thread.join();
     118             :         }
     119          13 :     }
     120             : 
     121          49 :     void stopped_check()
     122             :     {
     123             :         // Once in a STOPPED state, it cannot change to another
     124          49 :         if (state_ == virtual_cpu::execution_state::STOPPED) {
     125           6 :             throw execution_exception{"vCPU has been stopped", p_counter};
     126             :         }
     127          43 :     }
     128             : 
     129             :     bool bp_check(virtual_memory::iterator reg_it);
     130             : 
     131             :     void run(bool schedule_next = true);
     132             : 
     133             :     boost::asio::io_context ctx;
     134             :     boost::asio::executor_work_guard<boost::asio::io_context::executor_type> worker_guard_;
     135             :     std::thread thread;
     136             : 
     137             :     virtual_memory vmem;
     138             :     std::deque<input> input_queue_;
     139             :     std::unordered_map<math::ternary, breakpoint> bps;
     140             : 
     141             :     // vCPU Registers
     142             :     math::ternary a;
     143             :     virtual_memory::iterator c;
     144             :     virtual_memory::iterator d;
     145             :     std::size_t p_counter;
     146             : 
     147             :     state_signal_type state_sig;
     148             :     output_signal_type output_sig;
     149             :     breakpoint_hit_signal_type bp_hit_sig;
     150             : 
     151             : private:
     152             :     std::atomic<virtual_cpu::execution_state> state_;
     153             : };
     154             : 
     155          19 : virtual_cpu::virtual_cpu(virtual_memory vmem) :
     156          19 :     impl_{std::make_shared<impl_t>(std::move(vmem))}
     157             : {
     158          38 :     impl_->thread = std::thread{[impl = impl_]() {
     159          19 :         auto eptr = std::exception_ptr{};
     160             :         try {
     161          19 :             impl->ctx.run();
     162           3 :         } catch (std::exception&) {
     163           3 :             eptr = std::current_exception();
     164             :         }
     165          14 :         impl->set_state(execution_state::STOPPED, eptr);
     166          71 :     }};
     167          19 : }
     168             : 
     169          14 : virtual_cpu::~virtual_cpu()
     170             : {
     171          14 :     if (!impl_) [[unlikely]] {
     172           1 :         return;
     173             :     }
     174          13 :     impl_->stop();
     175          14 : }
     176             : 
     177          36 : void virtual_cpu::run()
     178             : {
     179          36 :     impl_check();
     180          35 :     impl_->stopped_check();
     181          33 :     boost::asio::post(impl_->ctx, [impl = impl_]() {
     182          65 :         if (impl->state() == execution_state::RUNNING ||
     183          32 :             impl->state() == execution_state::WAITING_FOR_INPUT) {
     184           3 :             return;
     185             :         }
     186             : 
     187          30 :         impl->set_state(execution_state::RUNNING);
     188          30 :         impl->run();
     189             :     });
     190          33 : }
     191             : 
     192           7 : void virtual_cpu::pause()
     193             : {
     194           7 :     impl_check();
     195           7 :     impl_->stopped_check();
     196           5 :     boost::asio::post(impl_->ctx, [impl = impl_]() {
     197           9 :         if (impl->state() == execution_state::PAUSED ||
     198           4 :             impl->state() == execution_state::WAITING_FOR_INPUT) {
     199           3 :             return;
     200             :         }
     201             : 
     202           2 :         impl->set_state(execution_state::PAUSED);
     203             :     });
     204           5 : }
     205             : 
     206           7 : void virtual_cpu::step()
     207             : {
     208           7 :     impl_check();
     209           7 :     impl_->stopped_check();
     210           5 :     boost::asio::post(impl_->ctx, [impl = impl_]() {
     211           5 :         if (impl->state() == execution_state::WAITING_FOR_INPUT) {
     212           2 :             return;
     213             :         }
     214             : 
     215           3 :         impl->set_state(execution_state::PAUSED);
     216           3 :         impl->run(false);
     217             :     });
     218           5 : }
     219             : 
     220           9 : void virtual_cpu::add_input(std::string data)
     221             : {
     222           9 :     impl_check();
     223           9 :     boost::asio::post(impl_->ctx, [impl = impl_, data = std::move(data)]() {
     224           9 :         impl->input_queue_.emplace_back(std::move(data));
     225           9 :         if (impl->state() == execution_state::WAITING_FOR_INPUT) {
     226           2 :             impl->set_state(execution_state::RUNNING);
     227           2 :             impl->run();
     228             :         }
     229           9 :     });
     230           9 : }
     231             : 
     232          13 : void virtual_cpu::add_breakpoint(math::ternary address, std::size_t ignore_count)
     233             : {
     234          13 :     impl_check();
     235          13 :     boost::asio::post(impl_->ctx, [impl = impl_, address, ignore_count]() {
     236          13 :         auto bp = impl_t::breakpoint{address, ignore_count};
     237          13 :         impl->bps.insert_or_assign(address, std::move(bp));
     238          13 :     });
     239          13 : }
     240             : 
     241           3 : void virtual_cpu::remove_breakpoint(math::ternary address)
     242             : {
     243           3 :     impl_check();
     244           3 :     boost::asio::post(impl_->ctx, [impl = impl_, address]() {
     245           3 :         impl->bps.erase(address);
     246           3 :     });
     247           3 : }
     248             : 
     249          13 : void virtual_cpu::address_value(math::ternary address,
     250             :                                 address_value_callback_type cb) const
     251             : {
     252          13 :     impl_check();
     253          13 :     boost::asio::post(impl_->ctx, [impl = impl_, address, cb = std::move(cb)]() {
     254          13 :         const auto value = impl->vmem[address];
     255          13 :         cb(address, value);
     256          13 :     });
     257          13 : }
     258             : 
     259          29 : void virtual_cpu::register_value(vcpu_register reg,
     260             :                                  register_value_callback_type cb) const
     261             : {
     262          29 :     impl_check();
     263          29 :     boost::asio::post(impl_->ctx, [impl = impl_, reg, cb = std::move(cb)]() {
     264          29 :         switch (reg) {
     265           9 :         case vcpu_register::A:
     266           9 :             cb(reg, {}, impl->a);
     267           9 :             break;
     268           9 :         case vcpu_register::C:
     269             :         {
     270             :             const auto address = static_cast<math::ternary::underlying_type>(
     271           9 :                                     std::distance(impl->vmem.begin(), impl->c));
     272           9 :             cb(reg, address, impl->vmem[address]);
     273           9 :             break;
     274             :         }
     275           9 :         case vcpu_register::D:
     276             :         {
     277             :             const auto address = static_cast<math::ternary::underlying_type>(
     278           9 :                                     std::distance(impl->vmem.begin(), impl->d));
     279           9 :             cb(reg, address, impl->vmem[address]);
     280           9 :             break;
     281             :         }
     282           2 :         default:
     283           2 :             throw execution_exception{
     284           4 :                 "Unhandled register query: " + std::to_string(static_cast<int>(reg)),
     285           2 :                 impl->p_counter
     286           4 :             };
     287             :         }
     288          27 :     });
     289          29 : }
     290             : 
     291             : virtual_cpu::state_signal_type::connection
     292          18 : virtual_cpu::register_for_state_signal(state_signal_type::slot_type slot)
     293             : {
     294          18 :     impl_check();
     295          18 :     return impl_->state_sig.connect(std::move(slot));
     296             : }
     297             : 
     298             : virtual_cpu::output_signal_type::connection
     299          18 : virtual_cpu::register_for_output_signal(output_signal_type::slot_type slot)
     300             : {
     301          18 :     impl_check();
     302          18 :     return impl_->output_sig.connect(std::move(slot));
     303             : }
     304             : 
     305             : virtual_cpu::breakpoint_hit_signal_type::connection
     306          18 : virtual_cpu::register_for_breakpoint_hit_signal(breakpoint_hit_signal_type::slot_type slot)
     307             : {
     308          18 :     impl_check();
     309          18 :     return impl_->bp_hit_sig.connect(std::move(slot));
     310             : }
     311             : 
     312         171 : void virtual_cpu::impl_check() const
     313             : {
     314         171 :     if (!impl_) [[unlikely]] {
     315           1 :         throw execution_exception{"vCPU backend destroyed, use-after-move?", 0};
     316             :     }
     317         170 : }
     318             : 
     319        3887 : bool virtual_cpu::impl_t::bp_check(virtual_memory::iterator reg_it)
     320             : {
     321             :     const auto address = static_cast<math::ternary::underlying_type>(
     322        3887 :         std::distance(vmem.begin(), reg_it)
     323        3887 :     );
     324        3887 :     auto it = bps.find(address);
     325        3887 :     if (it == bps.end()) {
     326        3831 :         return false;
     327          56 :     } else if (it->second()) {
     328          12 :         set_state(virtual_cpu::execution_state::PAUSED);
     329          12 :         bp_hit_sig(address);
     330          12 :         return true;
     331             :     }
     332          44 :     return false;
     333             : }
     334             : 
     335        3889 : void virtual_cpu::impl_t::run(bool schedule_next)
     336             : {
     337             :     // A pause() needs to break the run()-chain
     338        3889 :     if (state_ == virtual_cpu::execution_state::PAUSED && schedule_next) {
     339          31 :         return;
     340             :     }
     341             : 
     342        3887 :     if (bp_check(c)) {
     343          12 :         return;
     344             :     }
     345             : 
     346             :     // Pre-cipher the instruction
     347        3875 :     auto instr = pre_cipher_instruction(*c, c - vmem.begin());
     348        3875 :     if (!instr) {
     349           1 :         throw execution_exception{
     350             :             "Pre-cipher non-whitespace character must be graphical "
     351           2 :                 "ASCII: " + std::to_string(static_cast<int>(*c)),
     352             :             p_counter
     353           2 :         };
     354             :     }
     355             : 
     356        3874 :     log::print(log::VERBOSE_DEBUG,
     357        3874 :                "Step: ", p_counter, ", pre-cipher instr: ",
     358        3874 :                static_cast<int>(*instr));
     359             : 
     360        3874 :     switch (*instr) {
     361         935 :     case cpu_instruction::set_data_ptr:
     362         935 :         d = vmem.begin() + static_cast<std::size_t>(*d);
     363         935 :         break;
     364        1800 :     case cpu_instruction::set_code_ptr:
     365        1800 :         c = vmem.begin() + static_cast<std::size_t>(*d);
     366        1800 :         break;
     367         117 :     case cpu_instruction::rotate:
     368         117 :         a = d->rotate();
     369         117 :         break;
     370         166 :     case cpu_instruction::op:
     371         166 :         a = *d = a.op(*d);
     372         166 :         break;
     373          72 :     case cpu_instruction::read:
     374             :     {
     375          72 :         if (input_queue_.empty()) {
     376           7 :             set_state(virtual_cpu::execution_state::WAITING_FOR_INPUT);
     377           7 :             log::print(log::VERBOSE_DEBUG, "\tWaiting for input...");
     378           7 :             return;
     379             :         }
     380             : 
     381          65 :         auto c = input_queue_.front().get();
     382          65 :         if (!c) {
     383           9 :             a = math::ternary::max;
     384           9 :             input_queue_.pop_front();
     385             :         } else {
     386          56 :             a = c;
     387             :         }
     388          65 :         break;
     389             :     }
     390         189 :     case cpu_instruction::write:
     391         189 :         if (a != math::ternary::max) {
     392         180 :             output_sig(static_cast<char>(a));
     393             :         }
     394         189 :         break;
     395          10 :     case cpu_instruction::stop:
     396          10 :         set_state(virtual_cpu::execution_state::STOPPED);
     397          10 :         return;
     398         585 :     default:
     399             :         // Nop
     400         585 :         break;
     401             :     }
     402             : 
     403             :     // Post-cipher the instruction
     404        3857 :     auto pc = post_cipher_instruction(*c);
     405        3857 :     if (!pc) {
     406           0 :         throw execution_exception{
     407             :             "Post-cipher non-whitespace character must be graphical "
     408           0 :                 "ASCII: " + std::to_string(static_cast<int>(*c)),
     409             :             p_counter
     410           0 :         };
     411             :     }
     412        3857 :     *c = *pc;
     413             : 
     414        3857 :     log::print(log::VERBOSE_DEBUG,
     415        3857 :                "\tPost-op regs - a: ", a,
     416        3857 :                ", c[", std::distance(vmem.begin(), c), "]: ", *c,
     417        3857 :                ", d[", std::distance(vmem.begin(), d), "]: ", *d);
     418             : 
     419        3857 :     ++c;
     420        3857 :     ++d;
     421        3857 :     ++p_counter;
     422        3857 :     if (schedule_next) {
     423             :         // Schedule the next iteration
     424        3854 :         boost::asio::post(ctx, [impl = shared_from_this()]() {
     425        3854 :             impl->run();
     426        3853 :         });
     427             :     }
     428             : }
     429             : 
     430           8 : std::ostream& malbolge::operator<<(std::ostream& stream,
     431             :                                    virtual_cpu::vcpu_register register_id)
     432             : {
     433             :     static_assert(static_cast<int>(virtual_cpu::vcpu_register::NUM_REGISTERS) == 3,
     434             :                   "Register IDs have changed, update this");
     435             : 
     436           8 :     switch(register_id) {
     437           3 :     case virtual_cpu::vcpu_register::A:
     438           3 :         return stream << "A";
     439           1 :     case virtual_cpu::vcpu_register::C:
     440           1 :         return stream << "C";
     441           3 :     case virtual_cpu::vcpu_register::D:
     442           3 :         return stream << "D";
     443           1 :     default:
     444           1 :         return stream << "Unknown register ID: "
     445           1 :                       << static_cast<int>(register_id);
     446             :     }
     447             : }
     448             : 
     449           6 : std::ostream& malbolge::operator<<(std::ostream& stream,
     450             :                                    virtual_cpu::execution_state state)
     451             : {
     452             :     static_assert(static_cast<int>(virtual_cpu::execution_state::NUM_STATES) == 5,
     453             :                   "Number of execution states have change, update operator<<");
     454             : 
     455           6 :     switch (state) {
     456           1 :     case virtual_cpu::execution_state::READY:
     457           1 :         return stream << "READY";
     458           1 :     case virtual_cpu::execution_state::RUNNING:
     459           1 :         return stream << "RUNNING";
     460           1 :     case virtual_cpu::execution_state::PAUSED:
     461           1 :         return stream << "PAUSED";
     462           1 :     case virtual_cpu::execution_state::WAITING_FOR_INPUT:
     463           1 :         return stream << "WAITING_FOR_INPUT";
     464           1 :     case virtual_cpu::execution_state::STOPPED:
     465           1 :         return stream << "STOPPED";
     466           1 :     default:
     467           1 :         return stream << "Unknown vCPU state: " << static_cast<int>(state);
     468             :     }
     469             : }

Generated by: LCOV version 1.14