Compare commits
16 Commits
9996fd4833
...
4f5d9214ed
Author | SHA1 | Date | |
---|---|---|---|
4f5d9214ed | |||
d42d2ce533 | |||
236d12d7f5 | |||
e1b6cab890 | |||
8361f88718 | |||
2ec7ea4b41 | |||
b24965d321 | |||
244bf6d2f2 | |||
1a4465a371 | |||
fa82a50824 | |||
6dc17857da | |||
11a30caae8 | |||
ac1a26a10c | |||
7a199e122d | |||
d8c3d2e19c | |||
375755999a |
@ -96,6 +96,7 @@ protected:
|
|||||||
using super::gen_leave;
|
using super::gen_leave;
|
||||||
using super::gen_operation;
|
using super::gen_operation;
|
||||||
using super::gen_sync;
|
using super::gen_sync;
|
||||||
|
using super::gen_set_tval;
|
||||||
|
|
||||||
using this_class = vm_impl<ARCH>;
|
using this_class = vm_impl<ARCH>;
|
||||||
using compile_func = continuation_e (this_class::*)(virt_addr_t&, code_word_t, jit_holder&);
|
using compile_func = continuation_e (this_class::*)(virt_addr_t&, code_word_t, jit_holder&);
|
||||||
@ -163,6 +164,7 @@ private:
|
|||||||
cc.comment(fmt::format("${instr.name}_{:#x}:",pc.val).c_str());
|
cc.comment(fmt::format("${instr.name}_{:#x}:",pc.val).c_str());
|
||||||
gen_sync(jh, PRE_SYNC, ${idx});
|
gen_sync(jh, PRE_SYNC, ${idx});
|
||||||
mov(cc, jh.pc, pc.val);
|
mov(cc, jh.pc, pc.val);
|
||||||
|
gen_set_tval(jh, instr);
|
||||||
pc = pc+${instr.length/8};
|
pc = pc+${instr.length/8};
|
||||||
mov(cc, jh.next_pc, pc.val);
|
mov(cc, jh.next_pc, pc.val);
|
||||||
|
|
||||||
@ -171,23 +173,37 @@ private:
|
|||||||
/*generate behavior*/
|
/*generate behavior*/
|
||||||
<%instr.behavior.eachLine{%>${it}
|
<%instr.behavior.eachLine{%>${it}
|
||||||
<%}%>
|
<%}%>
|
||||||
gen_instr_epilogue(jh);
|
|
||||||
gen_sync(jh, POST_SYNC, ${idx});
|
gen_sync(jh, POST_SYNC, ${idx});
|
||||||
|
gen_instr_epilogue(jh);
|
||||||
return returnValue;
|
return returnValue;
|
||||||
}
|
}
|
||||||
<%}%>
|
<%}%>
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* end opcode definitions
|
* end opcode definitions
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
continuation_e illegal_intruction(virt_addr_t &pc, code_word_t instr, jit_holder& jh ) {
|
continuation_e illegal_instruction(virt_addr_t &pc, code_word_t instr, jit_holder& jh ) {
|
||||||
x86::Compiler& cc = jh.cc;
|
x86::Compiler& cc = jh.cc;
|
||||||
cc.comment(fmt::format("illegal_intruction{:#x}:",pc.val).c_str());
|
if(this->disass_enabled){
|
||||||
|
auto mnemonic = std::string("illegal_instruction");
|
||||||
|
InvokeNode* call_print_disass;
|
||||||
|
char* mnemonic_ptr = strdup(mnemonic.c_str());
|
||||||
|
jh.disass_collection.push_back(mnemonic_ptr);
|
||||||
|
jh.cc.invoke(&call_print_disass, &print_disass, FuncSignature::build<void, void *, uint64_t, char *>());
|
||||||
|
call_print_disass->setArg(0, jh.arch_if_ptr);
|
||||||
|
call_print_disass->setArg(1, pc.val);
|
||||||
|
call_print_disass->setArg(2, mnemonic_ptr);
|
||||||
|
}
|
||||||
|
cc.comment(fmt::format("illegal_instruction{:#x}:",pc.val).c_str());
|
||||||
gen_sync(jh, PRE_SYNC, instr_descr.size());
|
gen_sync(jh, PRE_SYNC, instr_descr.size());
|
||||||
|
mov(cc, jh.pc, pc.val);
|
||||||
|
gen_set_tval(jh, instr);
|
||||||
pc = pc + ((instr & 3) == 3 ? 4 : 2);
|
pc = pc + ((instr & 3) == 3 ? 4 : 2);
|
||||||
|
mov(cc, jh.next_pc, pc.val);
|
||||||
gen_instr_prologue(jh);
|
gen_instr_prologue(jh);
|
||||||
cc.comment("//behavior:");
|
cc.comment("//behavior:");
|
||||||
gen_instr_epilogue(jh);
|
gen_raise(jh, 0, 2);
|
||||||
gen_sync(jh, POST_SYNC, instr_descr.size());
|
gen_sync(jh, POST_SYNC, instr_descr.size());
|
||||||
|
gen_instr_epilogue(jh);
|
||||||
return BRANCH;
|
return BRANCH;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,7 +289,7 @@ continuation_e vm_impl<ARCH>::gen_single_inst_behavior(virt_addr_t &pc, unsigned
|
|||||||
++inst_cnt;
|
++inst_cnt;
|
||||||
auto f = decode_instr(root, instr);
|
auto f = decode_instr(root, instr);
|
||||||
if (f == nullptr)
|
if (f == nullptr)
|
||||||
f = &this_class::illegal_intruction;
|
f = &this_class::illegal_instruction;
|
||||||
return (this->*f)(pc, instr, jh);
|
return (this->*f)(pc, instr, jh);
|
||||||
}
|
}
|
||||||
template <typename ARCH>
|
template <typename ARCH>
|
||||||
@ -281,7 +297,6 @@ void vm_impl<ARCH>::gen_instr_prologue(jit_holder& jh) {
|
|||||||
auto& cc = jh.cc;
|
auto& cc = jh.cc;
|
||||||
|
|
||||||
cc.comment("//gen_instr_prologue");
|
cc.comment("//gen_instr_prologue");
|
||||||
cc.inc(get_ptr_for(jh, traits::ICOUNT));
|
|
||||||
|
|
||||||
x86_reg_t current_trap_state = get_reg_for(cc, traits::TRAP_STATE);
|
x86_reg_t current_trap_state = get_reg_for(cc, traits::TRAP_STATE);
|
||||||
mov(cc, current_trap_state, get_ptr_for(jh, traits::TRAP_STATE));
|
mov(cc, current_trap_state, get_ptr_for(jh, traits::TRAP_STATE));
|
||||||
@ -297,12 +312,13 @@ void vm_impl<ARCH>::gen_instr_epilogue(jit_holder& jh) {
|
|||||||
mov(cc, current_trap_state, get_ptr_for(jh, traits::TRAP_STATE));
|
mov(cc, current_trap_state, get_ptr_for(jh, traits::TRAP_STATE));
|
||||||
cmp(cc, current_trap_state, 0);
|
cmp(cc, current_trap_state, 0);
|
||||||
cc.jne(jh.trap_entry);
|
cc.jne(jh.trap_entry);
|
||||||
|
cc.inc(get_ptr_for(jh, traits::ICOUNT));
|
||||||
}
|
}
|
||||||
template <typename ARCH>
|
template <typename ARCH>
|
||||||
void vm_impl<ARCH>::gen_block_prologue(jit_holder& jh){
|
void vm_impl<ARCH>::gen_block_prologue(jit_holder& jh){
|
||||||
|
|
||||||
jh.pc = load_reg_from_mem_Gp(jh, traits::PC);
|
jh.pc = load_reg_from_mem_Gp(jh, traits::PC);
|
||||||
jh.next_pc = load_reg_from_mem_Gp(jh, traits::NEXT_PC);
|
jh.next_pc = load_reg_from_mem_Gp(jh, traits::NEXT_PC);
|
||||||
|
jh.tval = get_reg_Gp(jh.cc, 64, false);
|
||||||
}
|
}
|
||||||
template <typename ARCH>
|
template <typename ARCH>
|
||||||
void vm_impl<ARCH>::gen_block_epilogue(jit_holder& jh){
|
void vm_impl<ARCH>::gen_block_epilogue(jit_holder& jh){
|
||||||
@ -312,7 +328,6 @@ void vm_impl<ARCH>::gen_block_epilogue(jit_holder& jh){
|
|||||||
|
|
||||||
cc.bind(jh.trap_entry);
|
cc.bind(jh.trap_entry);
|
||||||
this->write_back(jh);
|
this->write_back(jh);
|
||||||
this->gen_sync(jh, POST_SYNC, -1);
|
|
||||||
|
|
||||||
x86::Gp current_trap_state = get_reg_for_Gp(cc, traits::TRAP_STATE);
|
x86::Gp current_trap_state = get_reg_for_Gp(cc, traits::TRAP_STATE);
|
||||||
mov(cc, current_trap_state, get_ptr_for(jh, traits::TRAP_STATE));
|
mov(cc, current_trap_state, get_ptr_for(jh, traits::TRAP_STATE));
|
||||||
@ -320,15 +335,13 @@ void vm_impl<ARCH>::gen_block_epilogue(jit_holder& jh){
|
|||||||
x86::Gp current_pc = get_reg_for_Gp(cc, traits::PC);
|
x86::Gp current_pc = get_reg_for_Gp(cc, traits::PC);
|
||||||
mov(cc, current_pc, get_ptr_for(jh, traits::PC));
|
mov(cc, current_pc, get_ptr_for(jh, traits::PC));
|
||||||
|
|
||||||
x86::Gp instr = cc.newInt32("instr");
|
|
||||||
mov(cc, instr, 0); // FIXME:this is not correct, should be instrId of trapping instr
|
|
||||||
cc.comment("//enter trap call;");
|
cc.comment("//enter trap call;");
|
||||||
InvokeNode* call_enter_trap;
|
InvokeNode* call_enter_trap;
|
||||||
cc.invoke(&call_enter_trap, &enter_trap, FuncSignature::build<uint64_t, void*, uint64_t, uint64_t, uint64_t>());
|
cc.invoke(&call_enter_trap, &enter_trap, FuncSignature::build<uint64_t, void*, uint64_t, uint64_t, uint64_t>());
|
||||||
call_enter_trap->setArg(0, jh.arch_if_ptr);
|
call_enter_trap->setArg(0, jh.arch_if_ptr);
|
||||||
call_enter_trap->setArg(1, current_trap_state);
|
call_enter_trap->setArg(1, current_trap_state);
|
||||||
call_enter_trap->setArg(2, current_pc);
|
call_enter_trap->setArg(2, current_pc);
|
||||||
call_enter_trap->setArg(3, instr);
|
call_enter_trap->setArg(3, jh.tval);
|
||||||
|
|
||||||
x86_reg_t current_next_pc = get_reg_for(cc, traits::NEXT_PC);
|
x86_reg_t current_next_pc = get_reg_for(cc, traits::NEXT_PC);
|
||||||
mov(cc, current_next_pc, get_ptr_for(jh, traits::NEXT_PC));
|
mov(cc, current_next_pc, get_ptr_for(jh, traits::NEXT_PC));
|
||||||
@ -344,7 +357,6 @@ inline void vm_impl<ARCH>::gen_raise(jit_holder& jh, uint16_t trap_id, uint16_t
|
|||||||
auto tmp1 = get_reg_for(cc, traits::TRAP_STATE);
|
auto tmp1 = get_reg_for(cc, traits::TRAP_STATE);
|
||||||
mov(cc, tmp1, 0x80ULL << 24 | (cause << 16) | trap_id);
|
mov(cc, tmp1, 0x80ULL << 24 | (cause << 16) | trap_id);
|
||||||
mov(cc, get_ptr_for(jh, traits::TRAP_STATE), tmp1);
|
mov(cc, get_ptr_for(jh, traits::TRAP_STATE), tmp1);
|
||||||
mov(cc, jh.next_pc, std::numeric_limits<uint32_t>::max());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace tgc5c
|
} // namespace tgc5c
|
||||||
|
@ -106,7 +106,6 @@ protected:
|
|||||||
inline void raise(uint16_t trap_id, uint16_t cause){
|
inline void raise(uint16_t trap_id, uint16_t cause){
|
||||||
auto trap_val = 0x80ULL << 24 | (cause << 16) | trap_id;
|
auto trap_val = 0x80ULL << 24 | (cause << 16) | trap_id;
|
||||||
this->core.reg.trap_state = trap_val;
|
this->core.reg.trap_state = trap_val;
|
||||||
this->template get_reg<uint${addrDataWidth}_t>(traits::NEXT_PC) = std::numeric_limits<uint${addrDataWidth}_t>::max();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void leave(unsigned lvl){
|
inline void leave(unsigned lvl){
|
||||||
@ -117,7 +116,12 @@ protected:
|
|||||||
this->core.wait_until(type);
|
this->core.wait_until(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void set_tval(uint64_t new_tval){
|
||||||
|
tval = new_tval;
|
||||||
|
}
|
||||||
|
|
||||||
uint64_t fetch_count{0};
|
uint64_t fetch_count{0};
|
||||||
|
uint64_t tval{0};
|
||||||
|
|
||||||
using yield_t = boost::coroutines2::coroutine<void>::push_type;
|
using yield_t = boost::coroutines2::coroutine<void>::push_type;
|
||||||
using coro_t = boost::coroutines2::coroutine<void>::pull_type;
|
using coro_t = boost::coroutines2::coroutine<void>::pull_type;
|
||||||
@ -341,7 +345,9 @@ typename vm_base<ARCH>::virt_addr_t vm_impl<ARCH>::execute_inst(finish_cond_e co
|
|||||||
// this->core.reg.trap_state = this->core.reg.pending_trap;
|
// this->core.reg.trap_state = this->core.reg.pending_trap;
|
||||||
// trap check
|
// trap check
|
||||||
if(trap_state!=0){
|
if(trap_state!=0){
|
||||||
super::core.enter_trap(trap_state, pc.val, instr);
|
//In case of Instruction address misaligned (cause = 0 and trapid = 0) need the targeted addr (in tval)
|
||||||
|
auto mcause = (trap_state>>16) & 0xff;
|
||||||
|
super::core.enter_trap(trap_state, pc.val, mcause ? instr:tval);
|
||||||
} else {
|
} else {
|
||||||
icount++;
|
icount++;
|
||||||
instret++;
|
instret++;
|
||||||
|
@ -102,7 +102,10 @@ protected:
|
|||||||
void gen_raise_trap(uint16_t trap_id, uint16_t cause);
|
void gen_raise_trap(uint16_t trap_id, uint16_t cause);
|
||||||
void gen_leave_trap(unsigned lvl);
|
void gen_leave_trap(unsigned lvl);
|
||||||
void gen_wait(unsigned type);
|
void gen_wait(unsigned type);
|
||||||
|
void set_tval(uint64_t new_tval);
|
||||||
|
void set_tval(Value* new_tval);
|
||||||
void gen_trap_behavior(BasicBlock *) override;
|
void gen_trap_behavior(BasicBlock *) override;
|
||||||
|
void gen_instr_prologue();
|
||||||
void gen_instr_epilogue(BasicBlock *bb);
|
void gen_instr_epilogue(BasicBlock *bb);
|
||||||
|
|
||||||
inline Value *gen_reg_load(unsigned i, unsigned level = 0) {
|
inline Value *gen_reg_load(unsigned i, unsigned level = 0) {
|
||||||
@ -162,18 +165,27 @@ private:
|
|||||||
<%}%>if(this->disass_enabled){
|
<%}%>if(this->disass_enabled){
|
||||||
/* generate console output when executing the command */<%instr.disass.eachLine{%>
|
/* generate console output when executing the command */<%instr.disass.eachLine{%>
|
||||||
${it}<%}%>
|
${it}<%}%>
|
||||||
|
std::vector<Value*> args {
|
||||||
|
this->core_ptr,
|
||||||
|
this->gen_const(64, pc.val),
|
||||||
|
this->builder.CreateGlobalStringPtr(mnemonic),
|
||||||
|
};
|
||||||
|
this->builder.CreateCall(this->mod->getFunction("print_disass"), args);
|
||||||
}
|
}
|
||||||
bb->setName(fmt::format("${instr.name}_0x{:X}",pc.val));
|
bb->setName(fmt::format("${instr.name}_0x{:X}",pc.val));
|
||||||
this->gen_sync(PRE_SYNC,${idx});
|
this->gen_sync(PRE_SYNC,${idx});
|
||||||
auto cur_pc_val = this->gen_const(32,pc.val);
|
|
||||||
|
this->gen_set_pc(pc, traits::PC);
|
||||||
|
this->set_tval(instr);
|
||||||
pc=pc+ ${instr.length/8};
|
pc=pc+ ${instr.length/8};
|
||||||
this->gen_set_pc(pc, traits::NEXT_PC);
|
this->gen_set_pc(pc, traits::NEXT_PC);
|
||||||
|
|
||||||
|
this->gen_instr_prologue();
|
||||||
/*generate behavior*/
|
/*generate behavior*/
|
||||||
<%instr.behavior.eachLine{%>${it}
|
<%instr.behavior.eachLine{%>${it}
|
||||||
<%}%>
|
<%}%>
|
||||||
|
this->gen_sync(POST_SYNC, ${idx});
|
||||||
this->gen_instr_epilogue(bb);
|
this->gen_instr_epilogue(bb);
|
||||||
this->gen_sync(POST_SYNC, ${idx});
|
|
||||||
this->builder.CreateBr(bb);
|
this->builder.CreateBr(bb);
|
||||||
return returnValue;
|
return returnValue;
|
||||||
}
|
}
|
||||||
@ -181,7 +193,16 @@ private:
|
|||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* end opcode definitions
|
* end opcode definitions
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
std::tuple<continuation_e, BasicBlock *> illegal_intruction(virt_addr_t &pc, code_word_t instr, BasicBlock *bb) {
|
std::tuple<continuation_e, BasicBlock *> illegal_instruction(virt_addr_t &pc, code_word_t instr, BasicBlock *bb) {
|
||||||
|
if(this->disass_enabled){
|
||||||
|
auto mnemonic = std::string("illegal_instruction");
|
||||||
|
std::vector<Value*> args {
|
||||||
|
this->core_ptr,
|
||||||
|
this->gen_const(64, pc.val),
|
||||||
|
this->builder.CreateGlobalStringPtr(mnemonic),
|
||||||
|
};
|
||||||
|
this->builder.CreateCall(this->mod->getFunction("print_disass"), args);
|
||||||
|
}
|
||||||
this->gen_sync(iss::PRE_SYNC, instr_descr.size());
|
this->gen_sync(iss::PRE_SYNC, instr_descr.size());
|
||||||
this->builder.CreateStore(this->builder.CreateLoad(this->get_typeptr(traits::NEXT_PC), get_reg_ptr(traits::NEXT_PC), true),
|
this->builder.CreateStore(this->builder.CreateLoad(this->get_typeptr(traits::NEXT_PC), get_reg_ptr(traits::NEXT_PC), true),
|
||||||
get_reg_ptr(traits::PC), true);
|
get_reg_ptr(traits::PC), true);
|
||||||
@ -190,9 +211,12 @@ private:
|
|||||||
this->gen_const(64U, 1)),
|
this->gen_const(64U, 1)),
|
||||||
get_reg_ptr(traits::ICOUNT), true);
|
get_reg_ptr(traits::ICOUNT), true);
|
||||||
pc = pc + ((instr & 3) == 3 ? 4 : 2);
|
pc = pc + ((instr & 3) == 3 ? 4 : 2);
|
||||||
|
this->set_tval(instr);
|
||||||
this->gen_raise_trap(0, 2); // illegal instruction trap
|
this->gen_raise_trap(0, 2); // illegal instruction trap
|
||||||
this->gen_sync(iss::POST_SYNC, instr_descr.size());
|
this->gen_sync(iss::POST_SYNC, instr_descr.size());
|
||||||
this->gen_instr_epilogue(this->leave_blk);
|
bb = this->leave_blk;
|
||||||
|
this->gen_instr_epilogue(bb);
|
||||||
|
this->builder.CreateBr(bb);
|
||||||
return std::make_tuple(BRANCH, nullptr);
|
return std::make_tuple(BRANCH, nullptr);
|
||||||
}
|
}
|
||||||
//decoding functionality
|
//decoding functionality
|
||||||
@ -293,7 +317,7 @@ vm_impl<ARCH>::gen_single_inst_behavior(virt_addr_t &pc, unsigned int &inst_cnt,
|
|||||||
++inst_cnt;
|
++inst_cnt;
|
||||||
auto f = decode_instr(root, instr);
|
auto f = decode_instr(root, instr);
|
||||||
if (f == nullptr) {
|
if (f == nullptr) {
|
||||||
f = &this_class::illegal_intruction;
|
f = &this_class::illegal_instruction;
|
||||||
}
|
}
|
||||||
return (this->*f)(pc, instr, this_block);
|
return (this->*f)(pc, instr, this_block);
|
||||||
}
|
}
|
||||||
@ -308,15 +332,12 @@ template <typename ARCH>
|
|||||||
void vm_impl<ARCH>::gen_raise_trap(uint16_t trap_id, uint16_t cause) {
|
void vm_impl<ARCH>::gen_raise_trap(uint16_t trap_id, uint16_t cause) {
|
||||||
auto *TRAP_val = this->gen_const(32, 0x80 << 24 | (cause << 16) | trap_id);
|
auto *TRAP_val = this->gen_const(32, 0x80 << 24 | (cause << 16) | trap_id);
|
||||||
this->builder.CreateStore(TRAP_val, get_reg_ptr(traits::TRAP_STATE), true);
|
this->builder.CreateStore(TRAP_val, get_reg_ptr(traits::TRAP_STATE), true);
|
||||||
this->builder.CreateStore(this->gen_const(32U, std::numeric_limits<uint32_t>::max()), get_reg_ptr(traits::LAST_BRANCH), false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename ARCH>
|
template <typename ARCH>
|
||||||
void vm_impl<ARCH>::gen_leave_trap(unsigned lvl) {
|
void vm_impl<ARCH>::gen_leave_trap(unsigned lvl) {
|
||||||
std::vector<Value *> args{ this->core_ptr, ConstantInt::get(getContext(), APInt(64, lvl)) };
|
std::vector<Value *> args{ this->core_ptr, ConstantInt::get(getContext(), APInt(64, lvl)) };
|
||||||
this->builder.CreateCall(this->mod->getFunction("leave_trap"), args);
|
this->builder.CreateCall(this->mod->getFunction("leave_trap"), args);
|
||||||
auto *PC_val = this->gen_read_mem(traits::CSR, (lvl << 8) + 0x41, traits::XLEN / 8);
|
|
||||||
this->builder.CreateStore(PC_val, get_reg_ptr(traits::NEXT_PC), false);
|
|
||||||
this->builder.CreateStore(this->gen_const(32U, std::numeric_limits<uint32_t>::max()), get_reg_ptr(traits::LAST_BRANCH), false);
|
this->builder.CreateStore(this->gen_const(32U, std::numeric_limits<uint32_t>::max()), get_reg_ptr(traits::LAST_BRANCH), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -326,19 +347,37 @@ void vm_impl<ARCH>::gen_wait(unsigned type) {
|
|||||||
this->builder.CreateCall(this->mod->getFunction("wait"), args);
|
this->builder.CreateCall(this->mod->getFunction("wait"), args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename ARCH>
|
||||||
|
inline void vm_impl<ARCH>::set_tval(uint64_t tval) {
|
||||||
|
auto tmp_tval = this->gen_const(64, tval);
|
||||||
|
this->set_tval(tmp_tval);
|
||||||
|
}
|
||||||
|
template <typename ARCH>
|
||||||
|
inline void vm_impl<ARCH>::set_tval(Value* new_tval) {
|
||||||
|
this->builder.CreateStore(this->gen_ext(new_tval, 64, false), this->tval);
|
||||||
|
}
|
||||||
template <typename ARCH>
|
template <typename ARCH>
|
||||||
void vm_impl<ARCH>::gen_trap_behavior(BasicBlock *trap_blk) {
|
void vm_impl<ARCH>::gen_trap_behavior(BasicBlock *trap_blk) {
|
||||||
this->builder.SetInsertPoint(trap_blk);
|
this->builder.SetInsertPoint(trap_blk);
|
||||||
this->gen_sync(POST_SYNC, -1); //TODO get right InstrId
|
|
||||||
auto *trap_state_val = this->builder.CreateLoad(this->get_typeptr(traits::TRAP_STATE), get_reg_ptr(traits::TRAP_STATE), true);
|
auto *trap_state_val = this->builder.CreateLoad(this->get_typeptr(traits::TRAP_STATE), get_reg_ptr(traits::TRAP_STATE), true);
|
||||||
this->builder.CreateStore(this->gen_const(32U, std::numeric_limits<uint32_t>::max()),
|
auto *cur_pc_val = this->builder.CreateLoad(this->get_typeptr(traits::PC), get_reg_ptr(traits::PC), true);
|
||||||
get_reg_ptr(traits::LAST_BRANCH), false);
|
std::vector<Value *> args{this->core_ptr,
|
||||||
std::vector<Value *> args{this->core_ptr, this->adj_to64(trap_state_val),
|
this->adj_to64(trap_state_val),
|
||||||
this->adj_to64(this->builder.CreateLoad(this->get_typeptr(traits::PC), get_reg_ptr(traits::PC), false))};
|
this->adj_to64(cur_pc_val),
|
||||||
|
this->adj_to64(this->builder.CreateLoad(this->get_type(64),this->tval))};
|
||||||
this->builder.CreateCall(this->mod->getFunction("enter_trap"), args);
|
this->builder.CreateCall(this->mod->getFunction("enter_trap"), args);
|
||||||
|
this->builder.CreateStore(this->gen_const(32U, std::numeric_limits<uint32_t>::max()), get_reg_ptr(traits::LAST_BRANCH), false);
|
||||||
|
|
||||||
auto *trap_addr_val = this->builder.CreateLoad(this->get_typeptr(traits::NEXT_PC), get_reg_ptr(traits::NEXT_PC), false);
|
auto *trap_addr_val = this->builder.CreateLoad(this->get_typeptr(traits::NEXT_PC), get_reg_ptr(traits::NEXT_PC), false);
|
||||||
this->builder.CreateRet(trap_addr_val);
|
this->builder.CreateRet(trap_addr_val);
|
||||||
}
|
}
|
||||||
|
template <typename ARCH>
|
||||||
|
void vm_impl<ARCH>::gen_instr_prologue() {
|
||||||
|
auto* trap_val =
|
||||||
|
this->builder.CreateLoad(this->get_typeptr(arch::traits<ARCH>::PENDING_TRAP), get_reg_ptr(arch::traits<ARCH>::PENDING_TRAP));
|
||||||
|
this->builder.CreateStore(trap_val, get_reg_ptr(arch::traits<ARCH>::TRAP_STATE), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
template <typename ARCH>
|
template <typename ARCH>
|
||||||
void vm_impl<ARCH>::gen_instr_epilogue(BasicBlock *bb) {
|
void vm_impl<ARCH>::gen_instr_epilogue(BasicBlock *bb) {
|
||||||
@ -349,6 +388,10 @@ void vm_impl<ARCH>::gen_instr_epilogue(BasicBlock *bb) {
|
|||||||
ConstantInt::get(getContext(), APInt(v->getType()->getIntegerBitWidth(), 0))),
|
ConstantInt::get(getContext(), APInt(v->getType()->getIntegerBitWidth(), 0))),
|
||||||
target_bb, this->trap_blk, 1);
|
target_bb, this->trap_blk, 1);
|
||||||
this->builder.SetInsertPoint(target_bb);
|
this->builder.SetInsertPoint(target_bb);
|
||||||
|
// update icount
|
||||||
|
auto* icount_val = this->builder.CreateAdd(
|
||||||
|
this->builder.CreateLoad(this->get_typeptr(arch::traits<ARCH>::ICOUNT), get_reg_ptr(arch::traits<ARCH>::ICOUNT)), this->gen_const(64U, 1));
|
||||||
|
this->builder.CreateStore(icount_val, get_reg_ptr(arch::traits<ARCH>::ICOUNT), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ${coreDef.name.toLowerCase()}
|
} // namespace ${coreDef.name.toLowerCase()}
|
||||||
|
@ -99,6 +99,10 @@ protected:
|
|||||||
|
|
||||||
void gen_wait(tu_builder& tu, unsigned type);
|
void gen_wait(tu_builder& tu, unsigned type);
|
||||||
|
|
||||||
|
inline void gen_set_tval(tu_builder& tu, uint64_t new_tval);
|
||||||
|
|
||||||
|
inline void gen_set_tval(tu_builder& tu, value new_tval);
|
||||||
|
|
||||||
inline void gen_trap_check(tu_builder& tu) {
|
inline void gen_trap_check(tu_builder& tu) {
|
||||||
tu("if(*trap_state!=0) goto trap_entry;");
|
tu("if(*trap_state!=0) goto trap_entry;");
|
||||||
}
|
}
|
||||||
@ -169,21 +173,27 @@ private:
|
|||||||
pc=pc+ ${instr.length/8};
|
pc=pc+ ${instr.length/8};
|
||||||
gen_set_pc(tu, pc, traits::NEXT_PC);
|
gen_set_pc(tu, pc, traits::NEXT_PC);
|
||||||
tu.open_scope();
|
tu.open_scope();
|
||||||
|
this->gen_set_tval(tu, instr);
|
||||||
<%instr.behavior.eachLine{%>${it}
|
<%instr.behavior.eachLine{%>${it}
|
||||||
<%}%>
|
<%}%>
|
||||||
tu.close_scope();
|
tu.close_scope();
|
||||||
gen_trap_check(tu);
|
|
||||||
vm_base<ARCH>::gen_sync(tu, POST_SYNC,${idx});
|
vm_base<ARCH>::gen_sync(tu, POST_SYNC,${idx});
|
||||||
|
gen_trap_check(tu);
|
||||||
return returnValue;
|
return returnValue;
|
||||||
}
|
}
|
||||||
<%}%>
|
<%}%>
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* end opcode definitions
|
* end opcode definitions
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
compile_ret_t illegal_intruction(virt_addr_t &pc, code_word_t instr, tu_builder& tu) {
|
compile_ret_t illegal_instruction(virt_addr_t &pc, code_word_t instr, tu_builder& tu) {
|
||||||
vm_impl::gen_sync(tu, iss::PRE_SYNC, instr_descr.size());
|
vm_impl::gen_sync(tu, iss::PRE_SYNC, instr_descr.size());
|
||||||
|
if(this->disass_enabled){
|
||||||
|
/* generate console output when executing the command */
|
||||||
|
tu("print_disass(core_ptr, {:#x}, \"{}\");", pc.val, std::string("illegal_instruction"));
|
||||||
|
}
|
||||||
pc = pc + ((instr & 3) == 3 ? 4 : 2);
|
pc = pc + ((instr & 3) == 3 ? 4 : 2);
|
||||||
gen_raise_trap(tu, 0, 2); // illegal instruction trap
|
gen_raise_trap(tu, 0, 2); // illegal instruction trap
|
||||||
|
this->gen_set_tval(tu, instr);
|
||||||
vm_impl::gen_sync(tu, iss::POST_SYNC, instr_descr.size());
|
vm_impl::gen_sync(tu, iss::POST_SYNC, instr_descr.size());
|
||||||
vm_impl::gen_trap_check(tu);
|
vm_impl::gen_trap_check(tu);
|
||||||
return BRANCH;
|
return BRANCH;
|
||||||
@ -285,7 +295,7 @@ vm_impl<ARCH>::gen_single_inst_behavior(virt_addr_t &pc, unsigned int &inst_cnt,
|
|||||||
++inst_cnt;
|
++inst_cnt;
|
||||||
auto f = decode_instr(root, instr);
|
auto f = decode_instr(root, instr);
|
||||||
if (f == nullptr) {
|
if (f == nullptr) {
|
||||||
f = &this_class::illegal_intruction;
|
f = &this_class::illegal_instruction;
|
||||||
}
|
}
|
||||||
return (this->*f)(pc, instr, tu);
|
return (this->*f)(pc, instr, tu);
|
||||||
}
|
}
|
||||||
@ -304,10 +314,17 @@ template <typename ARCH> void vm_impl<ARCH>::gen_leave_trap(tu_builder& tu, unsi
|
|||||||
template <typename ARCH> void vm_impl<ARCH>::gen_wait(tu_builder& tu, unsigned type) {
|
template <typename ARCH> void vm_impl<ARCH>::gen_wait(tu_builder& tu, unsigned type) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename ARCH> void vm_impl<ARCH>::gen_set_tval(tu_builder& tu, uint64_t new_tval) {
|
||||||
|
tu(fmt::format("tval = {};", new_tval));
|
||||||
|
}
|
||||||
|
template <typename ARCH> void vm_impl<ARCH>::gen_set_tval(tu_builder& tu, value new_tval) {
|
||||||
|
tu(fmt::format("tval = {};", new_tval.str));
|
||||||
|
}
|
||||||
|
|
||||||
template <typename ARCH> void vm_impl<ARCH>::gen_trap_behavior(tu_builder& tu) {
|
template <typename ARCH> void vm_impl<ARCH>::gen_trap_behavior(tu_builder& tu) {
|
||||||
tu("trap_entry:");
|
tu("trap_entry:");
|
||||||
this->gen_sync(tu, POST_SYNC, -1);
|
this->gen_sync(tu, POST_SYNC, -1);
|
||||||
tu("enter_trap(core_ptr, *trap_state, *pc, 0);");
|
tu("enter_trap(core_ptr, *trap_state, *pc, tval);");
|
||||||
tu.store(traits::LAST_BRANCH, tu.constant(std::numeric_limits<uint32_t>::max(),32));
|
tu.store(traits::LAST_BRANCH, tu.constant(std::numeric_limits<uint32_t>::max(),32));
|
||||||
tu("return *next_pc;");
|
tu("return *next_pc;");
|
||||||
}
|
}
|
||||||
|
@ -1207,7 +1207,7 @@ template <typename BASE, features_e FEAT> void riscv_hart_m_p<BASE, FEAT>::check
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename BASE, features_e FEAT> uint64_t riscv_hart_m_p<BASE, FEAT>::enter_trap(uint64_t flags, uint64_t addr, uint64_t instr) {
|
template <typename BASE, features_e FEAT> uint64_t riscv_hart_m_p<BASE, FEAT>::enter_trap(uint64_t flags, uint64_t addr, uint64_t tval) {
|
||||||
// flags are ACTIVE[31:31], CAUSE[30:16], TRAPID[15:0]
|
// flags are ACTIVE[31:31], CAUSE[30:16], TRAPID[15:0]
|
||||||
// calculate and write mcause val
|
// calculate and write mcause val
|
||||||
auto const trap_id = bit_sub<0, 16>(flags);
|
auto const trap_id = bit_sub<0, 16>(flags);
|
||||||
@ -1228,10 +1228,10 @@ template <typename BASE, features_e FEAT> uint64_t riscv_hart_m_p<BASE, FEAT>::e
|
|||||||
*/
|
*/
|
||||||
switch(cause) {
|
switch(cause) {
|
||||||
case 0:
|
case 0:
|
||||||
csr[mtval] = static_cast<reg_t>(addr);
|
csr[mtval] = static_cast<reg_t>(tval);
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
csr[mtval] = (!has_compressed() || (instr & 0x3) == 3) ? instr : instr & 0xffff;
|
csr[mtval] = (!has_compressed() || (tval & 0x3) == 3) ? tval : tval & 0xffff;
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
if((FEAT & FEAT_DEBUG) && (csr[dcsr] & 0x8000)) {
|
if((FEAT & FEAT_DEBUG) && (csr[dcsr] & 0x8000)) {
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -102,7 +102,6 @@ protected:
|
|||||||
inline void raise(uint16_t trap_id, uint16_t cause){
|
inline void raise(uint16_t trap_id, uint16_t cause){
|
||||||
auto trap_val = 0x80ULL << 24 | (cause << 16) | trap_id;
|
auto trap_val = 0x80ULL << 24 | (cause << 16) | trap_id;
|
||||||
this->core.reg.trap_state = trap_val;
|
this->core.reg.trap_state = trap_val;
|
||||||
this->template get_reg<uint32_t>(traits::NEXT_PC) = std::numeric_limits<uint32_t>::max();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void leave(unsigned lvl){
|
inline void leave(unsigned lvl){
|
||||||
@ -113,7 +112,12 @@ protected:
|
|||||||
this->core.wait_until(type);
|
this->core.wait_until(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void set_tval(uint64_t new_tval){
|
||||||
|
tval = new_tval;
|
||||||
|
}
|
||||||
|
|
||||||
uint64_t fetch_count{0};
|
uint64_t fetch_count{0};
|
||||||
|
uint64_t tval{0};
|
||||||
|
|
||||||
using yield_t = boost::coroutines2::coroutine<void>::push_type;
|
using yield_t = boost::coroutines2::coroutine<void>::push_type;
|
||||||
using coro_t = boost::coroutines2::coroutine<void>::pull_type;
|
using coro_t = boost::coroutines2::coroutine<void>::pull_type;
|
||||||
@ -463,14 +467,16 @@ typename vm_base<ARCH>::virt_addr_t vm_impl<ARCH>::execute_inst(finish_cond_e co
|
|||||||
raise(0, traits::RV_CAUSE_ILLEGAL_INSTRUCTION);
|
raise(0, traits::RV_CAUSE_ILLEGAL_INSTRUCTION);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if(imm % traits::INSTR_ALIGNMENT) {
|
uint32_t new_pc = (uint32_t)((uint64_t)(*PC ) + (uint64_t)((int32_t)sext<21>(imm) ));
|
||||||
|
if(new_pc % traits::INSTR_ALIGNMENT) {
|
||||||
|
set_tval(new_pc);
|
||||||
raise(0, 0);
|
raise(0, 0);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if(rd != 0) {
|
if(rd != 0) {
|
||||||
*(X+rd) = (uint32_t)((uint64_t)(*PC ) + (uint64_t)(4 ));
|
*(X+rd) = (uint32_t)((uint64_t)(*PC ) + (uint64_t)(4 ));
|
||||||
}
|
}
|
||||||
*NEXT_PC = (uint32_t)((uint64_t)(*PC ) + (uint64_t)((int32_t)sext<21>(imm) ));
|
*NEXT_PC = new_pc;
|
||||||
this->core.reg.last_branch = 1;
|
this->core.reg.last_branch = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -500,6 +506,7 @@ typename vm_base<ARCH>::virt_addr_t vm_impl<ARCH>::execute_inst(finish_cond_e co
|
|||||||
uint32_t addr_mask = (uint32_t)- 2;
|
uint32_t addr_mask = (uint32_t)- 2;
|
||||||
uint32_t new_pc = (uint32_t)(((uint64_t)(*(X+rs1) ) + (uint64_t)((int16_t)sext<12>(imm) )) & (int64_t)(addr_mask ));
|
uint32_t new_pc = (uint32_t)(((uint64_t)(*(X+rs1) ) + (uint64_t)((int16_t)sext<12>(imm) )) & (int64_t)(addr_mask ));
|
||||||
if(new_pc % traits::INSTR_ALIGNMENT) {
|
if(new_pc % traits::INSTR_ALIGNMENT) {
|
||||||
|
set_tval(new_pc);
|
||||||
raise(0, 0);
|
raise(0, 0);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -534,11 +541,13 @@ typename vm_base<ARCH>::virt_addr_t vm_impl<ARCH>::execute_inst(finish_cond_e co
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if(*(X+rs1) == *(X+rs2)) {
|
if(*(X+rs1) == *(X+rs2)) {
|
||||||
if((uint32_t)(imm ) % traits::INSTR_ALIGNMENT) {
|
uint32_t new_pc = (uint32_t)((uint64_t)(*PC ) + (uint64_t)((int16_t)sext<13>(imm) ));
|
||||||
|
if(new_pc % traits::INSTR_ALIGNMENT) {
|
||||||
|
set_tval(new_pc);
|
||||||
raise(0, 0);
|
raise(0, 0);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
*NEXT_PC = (uint32_t)((uint64_t)(*PC ) + (uint64_t)((int16_t)sext<13>(imm) ));
|
*NEXT_PC = new_pc;
|
||||||
this->core.reg.last_branch = 1;
|
this->core.reg.last_branch = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -567,11 +576,13 @@ typename vm_base<ARCH>::virt_addr_t vm_impl<ARCH>::execute_inst(finish_cond_e co
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if(*(X+rs1) != *(X+rs2)) {
|
if(*(X+rs1) != *(X+rs2)) {
|
||||||
if((uint32_t)(imm ) % traits::INSTR_ALIGNMENT) {
|
uint32_t new_pc = (uint32_t)((uint64_t)(*PC ) + (uint64_t)((int16_t)sext<13>(imm) ));
|
||||||
|
if(new_pc % traits::INSTR_ALIGNMENT) {
|
||||||
|
set_tval(new_pc);
|
||||||
raise(0, 0);
|
raise(0, 0);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
*NEXT_PC = (uint32_t)((uint64_t)(*PC ) + (uint64_t)((int16_t)sext<13>(imm) ));
|
*NEXT_PC = new_pc;
|
||||||
this->core.reg.last_branch = 1;
|
this->core.reg.last_branch = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -600,11 +611,13 @@ typename vm_base<ARCH>::virt_addr_t vm_impl<ARCH>::execute_inst(finish_cond_e co
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if((int32_t)*(X+rs1) < (int32_t)*(X+rs2)) {
|
if((int32_t)*(X+rs1) < (int32_t)*(X+rs2)) {
|
||||||
if((uint32_t)(imm ) % traits::INSTR_ALIGNMENT) {
|
uint32_t new_pc = (uint32_t)((uint64_t)(*PC ) + (uint64_t)((int16_t)sext<13>(imm) ));
|
||||||
|
if(new_pc % traits::INSTR_ALIGNMENT) {
|
||||||
|
set_tval(new_pc);
|
||||||
raise(0, 0);
|
raise(0, 0);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
*NEXT_PC = (uint32_t)((uint64_t)(*PC ) + (uint64_t)((int16_t)sext<13>(imm) ));
|
*NEXT_PC = new_pc;
|
||||||
this->core.reg.last_branch = 1;
|
this->core.reg.last_branch = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -633,11 +646,13 @@ typename vm_base<ARCH>::virt_addr_t vm_impl<ARCH>::execute_inst(finish_cond_e co
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if((int32_t)*(X+rs1) >= (int32_t)*(X+rs2)) {
|
if((int32_t)*(X+rs1) >= (int32_t)*(X+rs2)) {
|
||||||
if((uint32_t)(imm ) % traits::INSTR_ALIGNMENT) {
|
uint32_t new_pc = (uint32_t)((uint64_t)(*PC ) + (uint64_t)((int16_t)sext<13>(imm) ));
|
||||||
|
if(new_pc % traits::INSTR_ALIGNMENT) {
|
||||||
|
set_tval(new_pc);
|
||||||
raise(0, 0);
|
raise(0, 0);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
*NEXT_PC = (uint32_t)((uint64_t)(*PC ) + (uint64_t)((int16_t)sext<13>(imm) ));
|
*NEXT_PC = new_pc;
|
||||||
this->core.reg.last_branch = 1;
|
this->core.reg.last_branch = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -666,11 +681,13 @@ typename vm_base<ARCH>::virt_addr_t vm_impl<ARCH>::execute_inst(finish_cond_e co
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if(*(X+rs1) < *(X+rs2)) {
|
if(*(X+rs1) < *(X+rs2)) {
|
||||||
if((uint32_t)(imm ) % traits::INSTR_ALIGNMENT) {
|
uint32_t new_pc = (uint32_t)((uint64_t)(*PC ) + (uint64_t)((int16_t)sext<13>(imm) ));
|
||||||
|
if(new_pc % traits::INSTR_ALIGNMENT) {
|
||||||
|
set_tval(new_pc);
|
||||||
raise(0, 0);
|
raise(0, 0);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
*NEXT_PC = (uint32_t)((uint64_t)(*PC ) + (uint64_t)((int16_t)sext<13>(imm) ));
|
*NEXT_PC = new_pc;
|
||||||
this->core.reg.last_branch = 1;
|
this->core.reg.last_branch = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -699,11 +716,13 @@ typename vm_base<ARCH>::virt_addr_t vm_impl<ARCH>::execute_inst(finish_cond_e co
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if(*(X+rs1) >= *(X+rs2)) {
|
if(*(X+rs1) >= *(X+rs2)) {
|
||||||
if((uint32_t)(imm ) % traits::INSTR_ALIGNMENT) {
|
uint32_t new_pc = (uint32_t)((uint64_t)(*PC ) + (uint64_t)((int16_t)sext<13>(imm) ));
|
||||||
|
if(new_pc % traits::INSTR_ALIGNMENT) {
|
||||||
|
set_tval(new_pc);
|
||||||
raise(0, 0);
|
raise(0, 0);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
*NEXT_PC = (uint32_t)((uint64_t)(*PC ) + (uint64_t)((int16_t)sext<13>(imm) ));
|
*NEXT_PC = new_pc;
|
||||||
this->core.reg.last_branch = 1;
|
this->core.reg.last_branch = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2675,7 +2694,9 @@ typename vm_base<ARCH>::virt_addr_t vm_impl<ARCH>::execute_inst(finish_cond_e co
|
|||||||
// this->core.reg.trap_state = this->core.reg.pending_trap;
|
// this->core.reg.trap_state = this->core.reg.pending_trap;
|
||||||
// trap check
|
// trap check
|
||||||
if(trap_state!=0){
|
if(trap_state!=0){
|
||||||
super::core.enter_trap(trap_state, pc.val, instr);
|
//In case of Instruction address misaligned (cause = 0 and trapid = 0) need the targeted addr (in tval)
|
||||||
|
auto mcause = (trap_state>>16) & 0xff;
|
||||||
|
super::core.enter_trap(trap_state, pc.val, mcause ? instr:tval);
|
||||||
} else {
|
} else {
|
||||||
icount++;
|
icount++;
|
||||||
instret++;
|
instret++;
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user