Introduces decoder in a seperate class

This commit is contained in:
Eyck-Alexander Jentzsch 2024-07-23 13:08:53 +02:00
parent 6ee484a771
commit e3942be776
4 changed files with 83 additions and 288 deletions

View File

@ -37,6 +37,7 @@
#include <iss/asmjit/vm_base.h> #include <iss/asmjit/vm_base.h>
#include <asmjit/asmjit.h> #include <asmjit/asmjit.h>
#include <util/logging.h> #include <util/logging.h>
#include <vm/instruction_decoder.h>
#ifndef FMT_HEADER_ONLY #ifndef FMT_HEADER_ONLY
#define FMT_HEADER_ONLY #define FMT_HEADER_ONLY
@ -115,20 +116,11 @@ private:
* start opcode definitions * start opcode definitions
****************************************************************************/ ****************************************************************************/
struct instruction_descriptor { struct instruction_descriptor {
size_t length; uint32_t length;
uint32_t value; uint32_t value;
uint32_t mask; uint32_t mask;
compile_func op; compile_func op;
}; };
struct decoding_tree_node{
std::vector<instruction_descriptor> instrs;
std::vector<decoding_tree_node*> children;
uint32_t submask = std::numeric_limits<uint32_t>::max();
uint32_t value;
decoding_tree_node(uint32_t value) : value(value){}
};
decoding_tree_node* root {nullptr};
const std::array<instruction_descriptor, 87> instr_descr = {{ const std::array<instruction_descriptor, 87> instr_descr = {{
/* entries are: size, valid value, valid mask, function ptr */ /* entries are: size, valid value, valid mask, function ptr */
@ -308,6 +300,9 @@ private:
{16, 0b0000000000000000, 0b1111111111111111, &this_class::__dii}, {16, 0b0000000000000000, 0b1111111111111111, &this_class::__dii},
}}; }};
//needs to be declared after instr_descr
decoder instr_decoder;
/* instruction definitions */ /* instruction definitions */
/* instruction 0: LUI */ /* instruction 0: LUI */
continuation_e __lui(virt_addr_t& pc, code_word_t instr, jit_holder& jh){ continuation_e __lui(virt_addr_t& pc, code_word_t instr, jit_holder& jh){
@ -4740,72 +4735,22 @@ private:
gen_instr_epilogue(jh); gen_instr_epilogue(jh);
return BRANCH; return BRANCH;
} }
//decoding functionality
void populate_decoding_tree(decoding_tree_node* root){
//create submask
for(auto instr: root->instrs){
root->submask &= instr.mask;
}
//put each instr according to submask&encoding into children
for(auto instr: root->instrs){
bool foundMatch = false;
for(auto child: root->children){
//use value as identifying trait
if(child->value == (instr.value&root->submask)){
child->instrs.push_back(instr);
foundMatch = true;
}
}
if(!foundMatch){
decoding_tree_node* child = new decoding_tree_node(instr.value&root->submask);
child->instrs.push_back(instr);
root->children.push_back(child);
}
}
root->instrs.clear();
//call populate_decoding_tree for all children
if(root->children.size() >1)
for(auto child: root->children){
populate_decoding_tree(child);
}
else{
//sort instrs by value of the mask, this works bc we want to have the least restrictive one last
std::sort(root->children[0]->instrs.begin(), root->children[0]->instrs.end(), [](const instruction_descriptor& instr1, const instruction_descriptor& instr2) {
return instr1.mask > instr2.mask;
});
}
}
compile_func decode_instr(decoding_tree_node* node, code_word_t word){
if(!node->children.size()){
if(node->instrs.size() == 1) return node->instrs[0].op;
for(auto instr : node->instrs){
if((instr.mask&word) == instr.value) return instr.op;
}
}
else{
for(auto child : node->children){
if (child->value == (node->submask&word)){
return decode_instr(child, word);
}
}
}
return nullptr;
}
}; };
template <typename ARCH> vm_impl<ARCH>::vm_impl() { this(new ARCH()); } template <typename ARCH> vm_impl<ARCH>::vm_impl() { this(new ARCH()); }
template <typename ARCH> template <typename ARCH>
vm_impl<ARCH>::vm_impl(ARCH &core, unsigned core_id, unsigned cluster_id) vm_impl<ARCH>::vm_impl(ARCH &core, unsigned core_id, unsigned cluster_id)
: vm_base<ARCH>(core, core_id, cluster_id) { : vm_base<ARCH>(core, core_id, cluster_id)
root = new decoding_tree_node(std::numeric_limits<uint32_t>::max()); , instr_decoder([this]() {
for(auto instr: instr_descr){ std::vector<generic_instruction_descriptor> g_instr_descr;
root->instrs.push_back(instr); g_instr_descr.reserve(instr_descr.size());
for (uint32_t i = 0; i < instr_descr.size(); ++i) {
generic_instruction_descriptor new_instr_descr {instr_descr[i].value, instr_descr[i].mask, i};
g_instr_descr.push_back(new_instr_descr);
} }
populate_decoding_tree(root); return std::move(g_instr_descr);
} }()) {}
template <typename ARCH> template <typename ARCH>
continuation_e vm_impl<ARCH>::gen_single_inst_behavior(virt_addr_t &pc, unsigned int &inst_cnt, jit_holder& jh) { continuation_e vm_impl<ARCH>::gen_single_inst_behavior(virt_addr_t &pc, unsigned int &inst_cnt, jit_holder& jh) {
@ -4821,7 +4766,10 @@ continuation_e vm_impl<ARCH>::gen_single_inst_behavior(virt_addr_t &pc, unsigned
if (instr == 0x0000006f || (instr&0xffff)==0xa001) if (instr == 0x0000006f || (instr&0xffff)==0xa001)
throw simulation_stopped(0); // 'J 0' or 'C.J 0' throw simulation_stopped(0); // 'J 0' or 'C.J 0'
++inst_cnt; ++inst_cnt;
auto f = decode_instr(root, instr); uint32_t inst_index = instr_decoder.decode_instr(instr);
compile_func f = nullptr;
if(inst_index < instr_descr.size())
f = instr_descr[inst_index].op;
if (f == nullptr) if (f == nullptr)
f = &this_class::illegal_instruction; f = &this_class::illegal_instruction;
return (this->*f)(pc, instr, jh); return (this->*f)(pc, instr, jh);

View File

@ -31,6 +31,7 @@
*******************************************************************************/ *******************************************************************************/
// clang-format off // clang-format off
#include <cstdint>
#include <iss/arch/tgc5c.h> #include <iss/arch/tgc5c.h>
#include <iss/debugger/gdb_session.h> #include <iss/debugger/gdb_session.h>
#include <iss/debugger/server.h> #include <iss/debugger/server.h>
@ -43,6 +44,7 @@
#include <exception> #include <exception>
#include <vector> #include <vector>
#include <sstream> #include <sstream>
#include <vm/instruction_decoder.h>
#ifndef FMT_HEADER_ONLY #ifndef FMT_HEADER_ONLY
#define FMT_HEADER_ONLY #define FMT_HEADER_ONLY
@ -146,20 +148,11 @@ private:
* start opcode definitions * start opcode definitions
****************************************************************************/ ****************************************************************************/
struct instruction_descriptor { struct instruction_descriptor {
size_t length; uint32_t length;
uint32_t value; uint32_t value;
uint32_t mask; uint32_t mask;
typename arch::traits<ARCH>::opcode_e op; typename arch::traits<ARCH>::opcode_e op;
}; };
struct decoding_tree_node{
std::vector<instruction_descriptor> instrs;
std::vector<decoding_tree_node*> children;
uint32_t submask = std::numeric_limits<uint32_t>::max();
uint32_t value;
decoding_tree_node(uint32_t value) : value(value){}
};
decoding_tree_node* root {nullptr};
const std::array<instruction_descriptor, 87> instr_descr = {{ const std::array<instruction_descriptor, 87> instr_descr = {{
/* entries are: size, valid value, valid mask, function ptr */ /* entries are: size, valid value, valid mask, function ptr */
{32, 0b00000000000000000000000000110111, 0b00000000000000000000000001111111, arch::traits<ARCH>::opcode_e::LUI}, {32, 0b00000000000000000000000000110111, 0b00000000000000000000000001111111, arch::traits<ARCH>::opcode_e::LUI},
@ -250,6 +243,8 @@ private:
{16, 0b1100000000000010, 0b1110000000000011, arch::traits<ARCH>::opcode_e::C__SWSP}, {16, 0b1100000000000010, 0b1110000000000011, arch::traits<ARCH>::opcode_e::C__SWSP},
{16, 0b0000000000000000, 0b1111111111111111, arch::traits<ARCH>::opcode_e::DII}, {16, 0b0000000000000000, 0b1111111111111111, arch::traits<ARCH>::opcode_e::DII},
}}; }};
//needs to be declared after instr_descr
decoder instr_decoder;
iss::status fetch_ins(virt_addr_t pc, uint8_t * data){ iss::status fetch_ins(virt_addr_t pc, uint8_t * data){
if(this->core.has_mmu()) { if(this->core.has_mmu()) {
@ -270,58 +265,6 @@ private:
} }
return iss::Ok; return iss::Ok;
} }
void populate_decoding_tree(decoding_tree_node* root){
//create submask
for(auto instr: root->instrs){
root->submask &= instr.mask;
}
//put each instr according to submask&encoding into children
for(auto instr: root->instrs){
bool foundMatch = false;
for(auto child: root->children){
//use value as identifying trait
if(child->value == (instr.value&root->submask)){
child->instrs.push_back(instr);
foundMatch = true;
}
}
if(!foundMatch){
decoding_tree_node* child = new decoding_tree_node(instr.value&root->submask);
child->instrs.push_back(instr);
root->children.push_back(child);
}
}
root->instrs.clear();
//call populate_decoding_tree for all children
if(root->children.size() >1)
for(auto child: root->children){
populate_decoding_tree(child);
}
else{
//sort instrs by value of the mask, this works bc we want to have the least restrictive one last
std::sort(root->children[0]->instrs.begin(), root->children[0]->instrs.end(), [](const instruction_descriptor& instr1, const instruction_descriptor& instr2) {
return instr1.mask > instr2.mask;
});
}
}
typename arch::traits<ARCH>::opcode_e decode_instr(decoding_tree_node* node, code_word_t word){
if(!node->children.size()){
if(node->instrs.size() == 1) return node->instrs[0].op;
for(auto instr : node->instrs){
if((instr.mask&word) == instr.value) return instr.op;
}
}
else{
for(auto child : node->children){
if (child->value == (node->submask&word)){
return decode_instr(child, word);
}
}
}
return arch::traits<ARCH>::opcode_e::MAX_OPCODE;
}
}; };
template <typename CODE_WORD> void debug_fn(CODE_WORD insn) { template <typename CODE_WORD> void debug_fn(CODE_WORD insn) {
@ -347,13 +290,16 @@ constexpr size_t bit_count(uint32_t u) {
template <typename ARCH> template <typename ARCH>
vm_impl<ARCH>::vm_impl(ARCH &core, unsigned core_id, unsigned cluster_id) vm_impl<ARCH>::vm_impl(ARCH &core, unsigned core_id, unsigned cluster_id)
: vm_base<ARCH>(core, core_id, cluster_id) { : vm_base<ARCH>(core, core_id, cluster_id)
root = new decoding_tree_node(std::numeric_limits<uint32_t>::max()); , instr_decoder([this]() {
for(auto instr:instr_descr){ std::vector<generic_instruction_descriptor> g_instr_descr;
root->instrs.push_back(instr); g_instr_descr.reserve(instr_descr.size());
for (uint32_t i = 0; i < instr_descr.size(); ++i) {
generic_instruction_descriptor new_instr_descr {instr_descr[i].value, instr_descr[i].mask, i};
g_instr_descr.push_back(new_instr_descr);
} }
populate_decoding_tree(root); return std::move(g_instr_descr);
} }()) {}
inline bool is_icount_limit_enabled(finish_cond_e cond){ inline bool is_icount_limit_enabled(finish_cond_e cond){
return (cond & finish_cond_e::ICOUNT_LIMIT) == finish_cond_e::ICOUNT_LIMIT; return (cond & finish_cond_e::ICOUNT_LIMIT) == finish_cond_e::ICOUNT_LIMIT;
@ -390,7 +336,11 @@ typename vm_base<ARCH>::virt_addr_t vm_impl<ARCH>::execute_inst(finish_cond_e co
} else { } else {
if (is_jump_to_self_enabled(cond) && if (is_jump_to_self_enabled(cond) &&
(instr == 0x0000006f || (instr&0xffff)==0xa001)) throw simulation_stopped(0); // 'J 0' or 'C.J 0' (instr == 0x0000006f || (instr&0xffff)==0xa001)) throw simulation_stopped(0); // 'J 0' or 'C.J 0'
auto inst_id = decode_instr(root, instr); uint32_t inst_index = instr_decoder.decode_instr(instr);
opcode_e inst_id = arch::traits<ARCH>::opcode_e::MAX_OPCODE;;
if(inst_index <instr_descr.size())
inst_id = instr_descr.at(instr_decoder.decode_instr(instr)).op;
// pre execution stuff // pre execution stuff
this->core.reg.last_branch = 0; this->core.reg.last_branch = 0;
if(this->sync_exec && PRE_SYNC) this->do_sync(PRE_SYNC, static_cast<unsigned>(inst_id)); if(this->sync_exec && PRE_SYNC) this->do_sync(PRE_SYNC, static_cast<unsigned>(inst_id));

View File

@ -36,6 +36,7 @@
#include <iss/iss.h> #include <iss/iss.h>
#include <iss/llvm/vm_base.h> #include <iss/llvm/vm_base.h>
#include <util/logging.h> #include <util/logging.h>
#include <vm/instruction_decoder.h>
#ifndef FMT_HEADER_ONLY #ifndef FMT_HEADER_ONLY
#define FMT_HEADER_ONLY #define FMT_HEADER_ONLY
@ -136,20 +137,11 @@ private:
* start opcode definitions * start opcode definitions
****************************************************************************/ ****************************************************************************/
struct instruction_descriptor { struct instruction_descriptor {
size_t length; uint32_t length;
uint32_t value; uint32_t value;
uint32_t mask; uint32_t mask;
compile_func op; compile_func op;
}; };
struct decoding_tree_node{
std::vector<instruction_descriptor> instrs;
std::vector<decoding_tree_node*> children;
uint32_t submask = std::numeric_limits<uint32_t>::max();
uint32_t value;
decoding_tree_node(uint32_t value) : value(value){}
};
decoding_tree_node* root {nullptr};
const std::array<instruction_descriptor, 87> instr_descr = {{ const std::array<instruction_descriptor, 87> instr_descr = {{
/* entries are: size, valid value, valid mask, function ptr */ /* entries are: size, valid value, valid mask, function ptr */
@ -329,6 +321,9 @@ private:
{16, 0b0000000000000000, 0b1111111111111111, &this_class::__dii}, {16, 0b0000000000000000, 0b1111111111111111, &this_class::__dii},
}}; }};
//needs to be declared after instr_descr
decoder instr_decoder;
/* instruction definitions */ /* instruction definitions */
/* instruction 0: LUI */ /* instruction 0: LUI */
std::tuple<continuation_e, BasicBlock*> __lui(virt_addr_t& pc, code_word_t instr, BasicBlock* bb){ std::tuple<continuation_e, BasicBlock*> __lui(virt_addr_t& pc, code_word_t instr, BasicBlock* bb){
@ -4885,58 +4880,6 @@ private:
this->builder.CreateBr(bb); this->builder.CreateBr(bb);
return std::make_tuple(BRANCH, nullptr); return std::make_tuple(BRANCH, nullptr);
} }
//decoding functionality
void populate_decoding_tree(decoding_tree_node* root){
//create submask
for(auto instr: root->instrs){
root->submask &= instr.mask;
}
//put each instr according to submask&encoding into children
for(auto instr: root->instrs){
bool foundMatch = false;
for(auto child: root->children){
//use value as identifying trait
if(child->value == (instr.value&root->submask)){
child->instrs.push_back(instr);
foundMatch = true;
}
}
if(!foundMatch){
decoding_tree_node* child = new decoding_tree_node(instr.value&root->submask);
child->instrs.push_back(instr);
root->children.push_back(child);
}
}
root->instrs.clear();
//call populate_decoding_tree for all children
if(root->children.size() >1)
for(auto child: root->children){
populate_decoding_tree(child);
}
else{
//sort instrs by value of the mask, this works bc we want to have the least restrictive one last
std::sort(root->children[0]->instrs.begin(), root->children[0]->instrs.end(), [](const instruction_descriptor& instr1, const instruction_descriptor& instr2) {
return instr1.mask > instr2.mask;
});
}
}
compile_func decode_instr(decoding_tree_node* node, code_word_t word){
if(!node->children.size()){
if(node->instrs.size() == 1) return node->instrs[0].op;
for(auto instr : node->instrs){
if((instr.mask&word) == instr.value) return instr.op;
}
}
else{
for(auto child : node->children){
if (child->value == (node->submask&word)){
return decode_instr(child, word);
}
}
}
return nullptr;
}
}; };
template <typename CODE_WORD> void debug_fn(CODE_WORD instr) { template <typename CODE_WORD> void debug_fn(CODE_WORD instr) {
@ -4948,13 +4891,16 @@ template <typename ARCH> vm_impl<ARCH>::vm_impl() { this(new ARCH()); }
template <typename ARCH> template <typename ARCH>
vm_impl<ARCH>::vm_impl(ARCH &core, unsigned core_id, unsigned cluster_id) vm_impl<ARCH>::vm_impl(ARCH &core, unsigned core_id, unsigned cluster_id)
: vm_base<ARCH>(core, core_id, cluster_id) { : vm_base<ARCH>(core, core_id, cluster_id)
root = new decoding_tree_node(std::numeric_limits<uint32_t>::max()); , instr_decoder([this]() {
for(auto instr:instr_descr){ std::vector<generic_instruction_descriptor> g_instr_descr;
root->instrs.push_back(instr); g_instr_descr.reserve(instr_descr.size());
for (uint32_t i = 0; i < instr_descr.size(); ++i) {
generic_instruction_descriptor new_instr_descr {instr_descr[i].value, instr_descr[i].mask, i};
g_instr_descr.push_back(new_instr_descr);
} }
populate_decoding_tree(root); return std::move(g_instr_descr);
} }()) {}
template <typename ARCH> template <typename ARCH>
std::tuple<continuation_e, BasicBlock *> std::tuple<continuation_e, BasicBlock *>
@ -4981,7 +4927,10 @@ vm_impl<ARCH>::gen_single_inst_behavior(virt_addr_t &pc, unsigned int &inst_cnt,
if (instr == 0x0000006f || (instr&0xffff)==0xa001) throw simulation_stopped(0); // 'J 0' or 'C.J 0' if (instr == 0x0000006f || (instr&0xffff)==0xa001) throw simulation_stopped(0); // 'J 0' or 'C.J 0'
// curr pc on stack // curr pc on stack
++inst_cnt; ++inst_cnt;
auto f = decode_instr(root, instr); uint32_t inst_index = instr_decoder.decode_instr(instr);
compile_func f = nullptr;
if(inst_index < instr_descr.size())
f = instr_descr[inst_index].op;
if (f == nullptr) { if (f == nullptr) {
f = &this_class::illegal_instruction; f = &this_class::illegal_instruction;
} }

View File

@ -37,6 +37,7 @@
#include <iss/tcc/vm_base.h> #include <iss/tcc/vm_base.h>
#include <util/logging.h> #include <util/logging.h>
#include <sstream> #include <sstream>
#include <vm/instruction_decoder.h>
#ifndef FMT_HEADER_ONLY #ifndef FMT_HEADER_ONLY
#define FMT_HEADER_ONLY #define FMT_HEADER_ONLY
@ -137,20 +138,11 @@ private:
* start opcode definitions * start opcode definitions
****************************************************************************/ ****************************************************************************/
struct instruction_descriptor { struct instruction_descriptor {
size_t length; uint32_t length;
uint32_t value; uint32_t value;
uint32_t mask; uint32_t mask;
compile_func op; compile_func op;
}; };
struct decoding_tree_node{
std::vector<instruction_descriptor> instrs;
std::vector<decoding_tree_node*> children;
uint32_t submask = std::numeric_limits<uint32_t>::max();
uint32_t value;
decoding_tree_node(uint32_t value) : value(value){}
};
decoding_tree_node* root {nullptr};
const std::array<instruction_descriptor, 87> instr_descr = {{ const std::array<instruction_descriptor, 87> instr_descr = {{
/* entries are: size, valid value, valid mask, function ptr */ /* entries are: size, valid value, valid mask, function ptr */
@ -330,6 +322,9 @@ private:
{16, 0b0000000000000000, 0b1111111111111111, &this_class::__dii}, {16, 0b0000000000000000, 0b1111111111111111, &this_class::__dii},
}}; }};
//needs to be declared after instr_descr
decoder instr_decoder;
/* instruction definitions */ /* instruction definitions */
/* instruction 0: LUI */ /* instruction 0: LUI */
compile_ret_t __lui(virt_addr_t& pc, code_word_t instr, tu_builder& tu){ compile_ret_t __lui(virt_addr_t& pc, code_word_t instr, tu_builder& tu){
@ -3637,59 +3632,6 @@ private:
vm_impl::gen_trap_check(tu); vm_impl::gen_trap_check(tu);
return BRANCH; return BRANCH;
} }
//decoding functionality
void populate_decoding_tree(decoding_tree_node* root){
//create submask
for(auto instr: root->instrs){
root->submask &= instr.mask;
}
//put each instr according to submask&encoding into children
for(auto instr: root->instrs){
bool foundMatch = false;
for(auto child: root->children){
//use value as identifying trait
if(child->value == (instr.value&root->submask)){
child->instrs.push_back(instr);
foundMatch = true;
}
}
if(!foundMatch){
decoding_tree_node* child = new decoding_tree_node(instr.value&root->submask);
child->instrs.push_back(instr);
root->children.push_back(child);
}
}
root->instrs.clear();
//call populate_decoding_tree for all children
if(root->children.size() >1)
for(auto child: root->children){
populate_decoding_tree(child);
}
else{
//sort instrs by value of the mask, this works bc we want to have the least restrictive one last
std::sort(root->children[0]->instrs.begin(), root->children[0]->instrs.end(), [](const instruction_descriptor& instr1, const instruction_descriptor& instr2) {
return instr1.mask > instr2.mask;
});
}
}
compile_func decode_instr(decoding_tree_node* node, code_word_t word){
if(!node->children.size()){
if(node->instrs.size() == 1) return node->instrs[0].op;
for(auto instr : node->instrs){
if((instr.mask&word) == instr.value) return instr.op;
}
}
else{
for(auto child : node->children){
if (child->value == (node->submask&word)){
return decode_instr(child, word);
}
}
}
return nullptr;
}
}; };
template <typename CODE_WORD> void debug_fn(CODE_WORD instr) { template <typename CODE_WORD> void debug_fn(CODE_WORD instr) {
@ -3701,13 +3643,16 @@ template <typename ARCH> vm_impl<ARCH>::vm_impl() { this(new ARCH()); }
template <typename ARCH> template <typename ARCH>
vm_impl<ARCH>::vm_impl(ARCH &core, unsigned core_id, unsigned cluster_id) vm_impl<ARCH>::vm_impl(ARCH &core, unsigned core_id, unsigned cluster_id)
: vm_base<ARCH>(core, core_id, cluster_id) { : vm_base<ARCH>(core, core_id, cluster_id)
root = new decoding_tree_node(std::numeric_limits<uint32_t>::max()); , instr_decoder([this]() {
for(auto instr:instr_descr){ std::vector<generic_instruction_descriptor> g_instr_descr;
root->instrs.push_back(instr); g_instr_descr.reserve(instr_descr.size());
for (uint32_t i = 0; i < instr_descr.size(); ++i) {
generic_instruction_descriptor new_instr_descr {instr_descr[i].value, instr_descr[i].mask, i};
g_instr_descr.push_back(new_instr_descr);
} }
populate_decoding_tree(root); return std::move(g_instr_descr);
} }()) {}
template <typename ARCH> template <typename ARCH>
std::tuple<continuation_e> std::tuple<continuation_e>
@ -3732,7 +3677,10 @@ vm_impl<ARCH>::gen_single_inst_behavior(virt_addr_t &pc, unsigned int &inst_cnt,
if (instr == 0x0000006f || (instr&0xffff)==0xa001) throw simulation_stopped(0); // 'J 0' or 'C.J 0' if (instr == 0x0000006f || (instr&0xffff)==0xa001) throw simulation_stopped(0); // 'J 0' or 'C.J 0'
// curr pc on stack // curr pc on stack
++inst_cnt; ++inst_cnt;
auto f = decode_instr(root, instr); uint32_t inst_index = instr_decoder.decode_instr(instr);
compile_func f = nullptr;
if(inst_index < instr_descr.size())
f = instr_descr[inst_index].op;
if (f == nullptr) { if (f == nullptr) {
f = &this_class::illegal_instruction; f = &this_class::illegal_instruction;
} }