Avoid local when modrm address is single register without offset
This commit is contained in:
parent
c5dbd55d53
commit
23d4f862e6
|
@ -405,6 +405,21 @@ pub fn gen_modrm_fn2(builder: &mut WasmBuilder, name: &str, arg0: u32, arg1: u32
|
|||
pub fn gen_modrm_resolve(ctx: &mut JitContext, modrm_byte: ModrmByte) {
|
||||
modrm::gen(ctx, modrm_byte)
|
||||
}
|
||||
pub fn gen_modrm_resolve_with_local(
|
||||
ctx: &mut JitContext,
|
||||
modrm_byte: ModrmByte,
|
||||
gen: &dyn Fn(&mut JitContext, &WasmLocal),
|
||||
) {
|
||||
if let Some(r) = modrm::get_as_reg_index_if_possible(ctx, &modrm_byte) {
|
||||
gen(ctx, &ctx.reg(r));
|
||||
}
|
||||
else {
|
||||
gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address = ctx.builder.set_new_local();
|
||||
gen(ctx, &address);
|
||||
ctx.builder.free_local(address);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_set_reg8_r(ctx: &mut JitContext, dest: u32, src: u32) {
|
||||
// generates: reg8[r_dest] = reg8[r_src]
|
||||
|
@ -429,38 +444,25 @@ pub fn gen_set_reg32_r(ctx: &mut JitContext, dest: u32, src: u32) {
|
|||
}
|
||||
|
||||
pub fn gen_modrm_resolve_safe_read8(ctx: &mut JitContext, modrm_byte: ModrmByte) {
|
||||
gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
gen_safe_read8(ctx, &address_local);
|
||||
ctx.builder.free_local(address_local);
|
||||
gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| gen_safe_read8(ctx, addr));
|
||||
}
|
||||
pub fn gen_modrm_resolve_safe_read16(ctx: &mut JitContext, modrm_byte: ModrmByte) {
|
||||
gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
gen_safe_read16(ctx, &address_local);
|
||||
ctx.builder.free_local(address_local);
|
||||
gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| gen_safe_read16(ctx, addr));
|
||||
}
|
||||
pub fn gen_modrm_resolve_safe_read32(ctx: &mut JitContext, modrm_byte: ModrmByte) {
|
||||
gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
gen_safe_read32(ctx, &address_local);
|
||||
ctx.builder.free_local(address_local);
|
||||
gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| gen_safe_read32(ctx, addr));
|
||||
}
|
||||
pub fn gen_modrm_resolve_safe_read64(ctx: &mut JitContext, modrm_byte: ModrmByte) {
|
||||
gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
gen_safe_read64(ctx, &address_local);
|
||||
ctx.builder.free_local(address_local);
|
||||
gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| gen_safe_read64(ctx, addr));
|
||||
}
|
||||
pub fn gen_modrm_resolve_safe_read128(
|
||||
ctx: &mut JitContext,
|
||||
modrm_byte: ModrmByte,
|
||||
where_to_write: u32,
|
||||
) {
|
||||
gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
gen_safe_read128(ctx, &address_local, where_to_write);
|
||||
ctx.builder.free_local(address_local);
|
||||
gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| {
|
||||
gen_safe_read128(ctx, addr, where_to_write)
|
||||
});
|
||||
}
|
||||
|
||||
pub fn gen_safe_read8(ctx: &mut JitContext, address_local: &WasmLocal) {
|
||||
|
|
|
@ -487,16 +487,15 @@ macro_rules! mask_imm(
|
|||
macro_rules! define_instruction_read_write_mem8(
|
||||
($fn:expr, $name_mem:ident, $name_reg:ident, reg) => (
|
||||
pub fn $name_mem(ctx: &mut JitContext, modrm_byte: ModrmByte, r: u32) {
|
||||
codegen::gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
codegen::gen_safe_read_write(ctx, BitSize::BYTE, &address_local, &|ref mut ctx| {
|
||||
let dest_operand = ctx.builder.set_new_local();
|
||||
let source_operand = codegen::gen_get_reg8_or_alias_to_reg32(ctx, r);
|
||||
$fn(ctx, &dest_operand, &LocalOrImmediate::WasmLocal(&source_operand));
|
||||
codegen::gen_free_reg8_or_alias(ctx, r, source_operand);
|
||||
ctx.builder.free_local(dest_operand);
|
||||
codegen::gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| {
|
||||
codegen::gen_safe_read_write(ctx, BitSize::BYTE, &addr, &|ref mut ctx| {
|
||||
let dest_operand = ctx.builder.set_new_local();
|
||||
let source_operand = codegen::gen_get_reg8_or_alias_to_reg32(ctx, r);
|
||||
$fn(ctx, &dest_operand, &LocalOrImmediate::WasmLocal(&source_operand));
|
||||
codegen::gen_free_reg8_or_alias(ctx, r, source_operand);
|
||||
ctx.builder.free_local(dest_operand);
|
||||
});
|
||||
});
|
||||
ctx.builder.free_local(address_local);
|
||||
}
|
||||
|
||||
pub fn $name_reg(ctx: &mut JitContext, r1: u32, r2: u32) {
|
||||
|
@ -511,13 +510,12 @@ macro_rules! define_instruction_read_write_mem8(
|
|||
|
||||
($fn:expr, $name_mem:ident, $name_reg:ident, constant_one) => (
|
||||
pub fn $name_mem(ctx: &mut JitContext, modrm_byte: ModrmByte) {
|
||||
codegen::gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
codegen::gen_safe_read_write(ctx, BitSize::BYTE, &address_local, &|ref mut ctx| {
|
||||
ctx.builder.const_i32(1);
|
||||
ctx.builder.call_fn2_ret($fn);
|
||||
codegen::gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| {
|
||||
codegen::gen_safe_read_write(ctx, BitSize::BYTE, &addr, &|ref mut ctx| {
|
||||
ctx.builder.const_i32(1);
|
||||
ctx.builder.call_fn2_ret($fn);
|
||||
});
|
||||
});
|
||||
ctx.builder.free_local(address_local);
|
||||
}
|
||||
|
||||
pub fn $name_reg(ctx: &mut JitContext, r1: u32) {
|
||||
|
@ -530,15 +528,14 @@ macro_rules! define_instruction_read_write_mem8(
|
|||
|
||||
($fn:expr, $name_mem:ident, $name_reg:ident, cl) => (
|
||||
pub fn $name_mem(ctx: &mut JitContext, modrm_byte: ModrmByte) {
|
||||
codegen::gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
codegen::gen_safe_read_write(ctx, BitSize::BYTE, &address_local, &|ref mut ctx| {
|
||||
codegen::gen_get_reg8(ctx, regs::CL);
|
||||
ctx.builder.const_i32(31);
|
||||
ctx.builder.and_i32();
|
||||
ctx.builder.call_fn2_ret($fn);
|
||||
codegen::gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| {
|
||||
codegen::gen_safe_read_write(ctx, BitSize::BYTE, &addr, &|ref mut ctx| {
|
||||
codegen::gen_get_reg8(ctx, regs::CL);
|
||||
ctx.builder.const_i32(31);
|
||||
ctx.builder.and_i32();
|
||||
ctx.builder.call_fn2_ret($fn);
|
||||
});
|
||||
});
|
||||
ctx.builder.free_local(address_local);
|
||||
}
|
||||
|
||||
pub fn $name_reg(ctx: &mut JitContext, r1: u32) {
|
||||
|
@ -553,12 +550,11 @@ macro_rules! define_instruction_read_write_mem8(
|
|||
|
||||
($fn:expr, $name_mem:ident, $name_reg:ident, none) => (
|
||||
pub fn $name_mem(ctx: &mut JitContext, modrm_byte: ModrmByte) {
|
||||
codegen::gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
codegen::gen_safe_read_write(ctx, BitSize::BYTE, &address_local, &|ref mut ctx| {
|
||||
ctx.builder.call_fn1_ret($fn);
|
||||
codegen::gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| {
|
||||
codegen::gen_safe_read_write(ctx, BitSize::BYTE, &addr, &|ref mut ctx| {
|
||||
ctx.builder.call_fn1_ret($fn);
|
||||
});
|
||||
});
|
||||
ctx.builder.free_local(address_local);
|
||||
}
|
||||
|
||||
pub fn $name_reg(ctx: &mut JitContext, r1: u32) {
|
||||
|
@ -570,14 +566,13 @@ macro_rules! define_instruction_read_write_mem8(
|
|||
|
||||
($fn:expr, $name_mem:ident, $name_reg:ident, ximm8) => (
|
||||
pub fn $name_mem(ctx: &mut JitContext, modrm_byte: ModrmByte, imm: u32) {
|
||||
codegen::gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
codegen::gen_safe_read_write(ctx, BitSize::BYTE, &address_local, &|ref mut ctx| {
|
||||
let dest_operand = ctx.builder.set_new_local();
|
||||
$fn(ctx, &dest_operand, &LocalOrImmediate::Immediate(imm as i32));
|
||||
ctx.builder.free_local(dest_operand);
|
||||
codegen::gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| {
|
||||
codegen::gen_safe_read_write(ctx, BitSize::BYTE, &addr, &|ref mut ctx| {
|
||||
let dest_operand = ctx.builder.set_new_local();
|
||||
$fn(ctx, &dest_operand, &LocalOrImmediate::Immediate(imm as i32));
|
||||
ctx.builder.free_local(dest_operand);
|
||||
});
|
||||
});
|
||||
ctx.builder.free_local(address_local);
|
||||
}
|
||||
|
||||
pub fn $name_reg(ctx: &mut JitContext, r1: u32, imm: u32) {
|
||||
|
@ -590,14 +585,13 @@ macro_rules! define_instruction_read_write_mem8(
|
|||
|
||||
($fn:expr, $name_mem:ident, $name_reg:ident, $imm:ident) => (
|
||||
pub fn $name_mem(ctx: &mut JitContext, modrm_byte: ModrmByte, imm: u32) {
|
||||
codegen::gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
let imm = mask_imm!(imm, $imm) as i32;
|
||||
codegen::gen_safe_read_write(ctx, BitSize::BYTE, &address_local, &|ref mut ctx| {
|
||||
ctx.builder.const_i32(imm as i32);
|
||||
ctx.builder.call_fn2_ret($fn);
|
||||
codegen::gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| {
|
||||
let imm = mask_imm!(imm, $imm) as i32;
|
||||
codegen::gen_safe_read_write(ctx, BitSize::BYTE, &addr, &|ref mut ctx| {
|
||||
ctx.builder.const_i32(imm as i32);
|
||||
ctx.builder.call_fn2_ret($fn);
|
||||
});
|
||||
});
|
||||
ctx.builder.free_local(address_local);
|
||||
}
|
||||
|
||||
pub fn $name_reg(ctx: &mut JitContext, r1: u32, imm: u32) {
|
||||
|
@ -613,13 +607,12 @@ macro_rules! define_instruction_read_write_mem8(
|
|||
macro_rules! define_instruction_read_write_mem16(
|
||||
($fn:expr, $name_mem:ident, $name_reg:ident, reg) => (
|
||||
pub fn $name_mem(ctx: &mut JitContext, modrm_byte: ModrmByte, r: u32) {
|
||||
codegen::gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
codegen::gen_safe_read_write(ctx, BitSize::WORD, &address_local, &|ref mut ctx| {
|
||||
codegen::gen_get_reg16(ctx, r);
|
||||
ctx.builder.call_fn2_ret($fn);
|
||||
codegen::gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| {
|
||||
codegen::gen_safe_read_write(ctx, BitSize::WORD, &addr, &|ref mut ctx| {
|
||||
codegen::gen_get_reg16(ctx, r);
|
||||
ctx.builder.call_fn2_ret($fn);
|
||||
});
|
||||
});
|
||||
ctx.builder.free_local(address_local);
|
||||
}
|
||||
|
||||
pub fn $name_reg(ctx: &mut JitContext, r1: u32, r2: u32) {
|
||||
|
@ -632,13 +625,12 @@ macro_rules! define_instruction_read_write_mem16(
|
|||
|
||||
($fn:expr, $name_mem:ident, $name_reg:ident, constant_one) => (
|
||||
pub fn $name_mem(ctx: &mut JitContext, modrm_byte: ModrmByte) {
|
||||
codegen::gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
codegen::gen_safe_read_write(ctx, BitSize::WORD, &address_local, &|ref mut ctx| {
|
||||
ctx.builder.const_i32(1);
|
||||
ctx.builder.call_fn2_ret($fn);
|
||||
codegen::gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| {
|
||||
codegen::gen_safe_read_write(ctx, BitSize::WORD, &addr, &|ref mut ctx| {
|
||||
ctx.builder.const_i32(1);
|
||||
ctx.builder.call_fn2_ret($fn);
|
||||
});
|
||||
});
|
||||
ctx.builder.free_local(address_local);
|
||||
}
|
||||
|
||||
pub fn $name_reg(ctx: &mut JitContext, r1: u32) {
|
||||
|
@ -651,15 +643,14 @@ macro_rules! define_instruction_read_write_mem16(
|
|||
|
||||
($fn:expr, $name_mem:ident, $name_reg:ident, cl) => (
|
||||
pub fn $name_mem(ctx: &mut JitContext, modrm_byte: ModrmByte) {
|
||||
codegen::gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
codegen::gen_safe_read_write(ctx, BitSize::WORD, &address_local, &|ref mut ctx| {
|
||||
codegen::gen_get_reg8(ctx, regs::CL);
|
||||
ctx.builder.const_i32(31);
|
||||
ctx.builder.and_i32();
|
||||
ctx.builder.call_fn2_ret($fn);
|
||||
codegen::gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| {
|
||||
codegen::gen_safe_read_write(ctx, BitSize::WORD, &addr, &|ref mut ctx| {
|
||||
codegen::gen_get_reg8(ctx, regs::CL);
|
||||
ctx.builder.const_i32(31);
|
||||
ctx.builder.and_i32();
|
||||
ctx.builder.call_fn2_ret($fn);
|
||||
});
|
||||
});
|
||||
ctx.builder.free_local(address_local);
|
||||
}
|
||||
|
||||
pub fn $name_reg(ctx: &mut JitContext, r1: u32) {
|
||||
|
@ -674,16 +665,15 @@ macro_rules! define_instruction_read_write_mem16(
|
|||
|
||||
($fn:expr, $name_mem:ident, $name_reg:ident, reg, cl) => (
|
||||
pub fn $name_mem(ctx: &mut JitContext, modrm_byte: ModrmByte, r: u32) {
|
||||
codegen::gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
codegen::gen_safe_read_write(ctx, BitSize::WORD, &address_local, &|ref mut ctx| {
|
||||
codegen::gen_get_reg16(ctx, r);
|
||||
codegen::gen_get_reg8(ctx, regs::CL);
|
||||
ctx.builder.const_i32(31);
|
||||
ctx.builder.and_i32();
|
||||
ctx.builder.call_fn3_ret($fn);
|
||||
codegen::gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| {
|
||||
codegen::gen_safe_read_write(ctx, BitSize::WORD, &addr, &|ref mut ctx| {
|
||||
codegen::gen_get_reg16(ctx, r);
|
||||
codegen::gen_get_reg8(ctx, regs::CL);
|
||||
ctx.builder.const_i32(31);
|
||||
ctx.builder.and_i32();
|
||||
ctx.builder.call_fn3_ret($fn);
|
||||
});
|
||||
});
|
||||
ctx.builder.free_local(address_local);
|
||||
}
|
||||
|
||||
pub fn $name_reg(ctx: &mut JitContext, r1: u32, r2: u32) {
|
||||
|
@ -699,15 +689,14 @@ macro_rules! define_instruction_read_write_mem16(
|
|||
|
||||
($fn:expr, $name_mem:ident, $name_reg:ident, reg, $imm:ident) => (
|
||||
pub fn $name_mem(ctx: &mut JitContext, modrm_byte: ModrmByte, r: u32, imm: u32) {
|
||||
codegen::gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
let imm = mask_imm!(imm, $imm);
|
||||
codegen::gen_safe_read_write(ctx, BitSize::WORD, &address_local, &|ref mut ctx| {
|
||||
codegen::gen_get_reg16(ctx, r);
|
||||
ctx.builder.const_i32(imm as i32);
|
||||
ctx.builder.call_fn3_ret($fn);
|
||||
codegen::gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| {
|
||||
let imm = mask_imm!(imm, $imm);
|
||||
codegen::gen_safe_read_write(ctx, BitSize::WORD, &addr, &|ref mut ctx| {
|
||||
codegen::gen_get_reg16(ctx, r);
|
||||
ctx.builder.const_i32(imm as i32);
|
||||
ctx.builder.call_fn3_ret($fn);
|
||||
});
|
||||
});
|
||||
ctx.builder.free_local(address_local);
|
||||
}
|
||||
|
||||
pub fn $name_reg(ctx: &mut JitContext, r1: u32, r2: u32, imm: u32) {
|
||||
|
@ -722,15 +711,14 @@ macro_rules! define_instruction_read_write_mem16(
|
|||
|
||||
($fn:expr, $name_mem:ident, $name_reg:ident, none) => (
|
||||
pub fn $name_mem(ctx: &mut JitContext, modrm_byte: ModrmByte) {
|
||||
codegen::gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
codegen::gen_safe_read_write(ctx, BitSize::WORD, &address_local, &|ref mut ctx| {
|
||||
let mut dest_operand = ctx.builder.set_new_local();
|
||||
$fn(ctx, &mut dest_operand);
|
||||
ctx.builder.get_local(&dest_operand);
|
||||
ctx.builder.free_local(dest_operand);
|
||||
codegen::gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| {
|
||||
codegen::gen_safe_read_write(ctx, BitSize::WORD, &addr, &|ref mut ctx| {
|
||||
let mut dest_operand = ctx.builder.set_new_local();
|
||||
$fn(ctx, &mut dest_operand);
|
||||
ctx.builder.get_local(&dest_operand);
|
||||
ctx.builder.free_local(dest_operand);
|
||||
});
|
||||
});
|
||||
ctx.builder.free_local(address_local);
|
||||
}
|
||||
|
||||
pub fn $name_reg(ctx: &mut JitContext, r1: u32) {
|
||||
|
@ -740,14 +728,13 @@ macro_rules! define_instruction_read_write_mem16(
|
|||
|
||||
($fn:expr, $name_mem:ident, $name_reg:ident, $imm:ident) => (
|
||||
pub fn $name_mem(ctx: &mut JitContext, modrm_byte: ModrmByte, imm: u32) {
|
||||
codegen::gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
let imm = mask_imm!(imm, $imm) as i32;
|
||||
codegen::gen_safe_read_write(ctx, BitSize::WORD, &address_local, &|ref mut ctx| {
|
||||
ctx.builder.const_i32(imm as i32);
|
||||
ctx.builder.call_fn2_ret($fn);
|
||||
codegen::gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| {
|
||||
let imm = mask_imm!(imm, $imm) as i32;
|
||||
codegen::gen_safe_read_write(ctx, BitSize::WORD, &addr, &|ref mut ctx| {
|
||||
ctx.builder.const_i32(imm as i32);
|
||||
ctx.builder.call_fn2_ret($fn);
|
||||
});
|
||||
});
|
||||
ctx.builder.free_local(address_local);
|
||||
}
|
||||
|
||||
pub fn $name_reg(ctx: &mut JitContext, r1: u32, imm: u32) {
|
||||
|
@ -763,19 +750,18 @@ macro_rules! define_instruction_read_write_mem16(
|
|||
macro_rules! define_instruction_read_write_mem32(
|
||||
($fn:expr, $name_mem:ident, $name_reg:ident, reg) => (
|
||||
pub fn $name_mem(ctx: &mut JitContext, modrm_byte: ModrmByte, r: u32) {
|
||||
codegen::gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
codegen::gen_safe_read_write(ctx, BitSize::DWORD, &address_local, &|ref mut ctx| {
|
||||
let dest_operand = ctx.builder.set_new_local();
|
||||
$fn(
|
||||
ctx,
|
||||
&dest_operand,
|
||||
&LocalOrImmediate::WasmLocal(&ctx.reg(r)),
|
||||
);
|
||||
ctx.builder.get_local(&dest_operand);
|
||||
ctx.builder.free_local(dest_operand);
|
||||
codegen::gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| {
|
||||
codegen::gen_safe_read_write(ctx, BitSize::DWORD, &addr, &|ref mut ctx| {
|
||||
let dest_operand = ctx.builder.set_new_local();
|
||||
$fn(
|
||||
ctx,
|
||||
&dest_operand,
|
||||
&LocalOrImmediate::WasmLocal(&ctx.reg(r)),
|
||||
);
|
||||
ctx.builder.get_local(&dest_operand);
|
||||
ctx.builder.free_local(dest_operand);
|
||||
});
|
||||
});
|
||||
ctx.builder.free_local(address_local);
|
||||
}
|
||||
|
||||
pub fn $name_reg(ctx: &mut JitContext, r1: u32, r2: u32) {
|
||||
|
@ -789,15 +775,14 @@ macro_rules! define_instruction_read_write_mem32(
|
|||
|
||||
($fn:expr, $name_mem:ident, $name_reg:ident, constant_one) => (
|
||||
pub fn $name_mem(ctx: &mut JitContext, modrm_byte: ModrmByte) {
|
||||
codegen::gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
codegen::gen_safe_read_write(ctx, BitSize::DWORD, &address_local, &|ref mut ctx| {
|
||||
let dest_operand = ctx.builder.set_new_local();
|
||||
$fn(ctx, &dest_operand, &LocalOrImmediate::Immediate(1));
|
||||
ctx.builder.get_local(&dest_operand);
|
||||
ctx.builder.free_local(dest_operand);
|
||||
codegen::gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| {
|
||||
codegen::gen_safe_read_write(ctx, BitSize::DWORD, &addr, &|ref mut ctx| {
|
||||
let dest_operand = ctx.builder.set_new_local();
|
||||
$fn(ctx, &dest_operand, &LocalOrImmediate::Immediate(1));
|
||||
ctx.builder.get_local(&dest_operand);
|
||||
ctx.builder.free_local(dest_operand);
|
||||
});
|
||||
});
|
||||
ctx.builder.free_local(address_local);
|
||||
}
|
||||
|
||||
pub fn $name_reg(ctx: &mut JitContext, r1: u32) {
|
||||
|
@ -807,19 +792,18 @@ macro_rules! define_instruction_read_write_mem32(
|
|||
|
||||
($fn:expr, $name_mem:ident, $name_reg:ident, cl) => (
|
||||
pub fn $name_mem(ctx: &mut JitContext, modrm_byte: ModrmByte) {
|
||||
codegen::gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
codegen::gen_safe_read_write(ctx, BitSize::DWORD, &address_local, &|ref mut ctx| {
|
||||
let dest_operand = ctx.builder.set_new_local();
|
||||
$fn(
|
||||
ctx,
|
||||
&dest_operand,
|
||||
&LocalOrImmediate::WasmLocal(&ctx.reg(regs::ECX)),
|
||||
);
|
||||
ctx.builder.get_local(&dest_operand);
|
||||
ctx.builder.free_local(dest_operand);
|
||||
codegen::gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| {
|
||||
codegen::gen_safe_read_write(ctx, BitSize::DWORD, &addr, &|ref mut ctx| {
|
||||
let dest_operand = ctx.builder.set_new_local();
|
||||
$fn(
|
||||
ctx,
|
||||
&dest_operand,
|
||||
&LocalOrImmediate::WasmLocal(&ctx.reg(regs::ECX)),
|
||||
);
|
||||
ctx.builder.get_local(&dest_operand);
|
||||
ctx.builder.free_local(dest_operand);
|
||||
});
|
||||
});
|
||||
ctx.builder.free_local(address_local);
|
||||
}
|
||||
|
||||
pub fn $name_reg(ctx: &mut JitContext, r1: u32) {
|
||||
|
@ -833,16 +817,15 @@ macro_rules! define_instruction_read_write_mem32(
|
|||
|
||||
($fn:expr, $name_mem:ident, $name_reg:ident, reg, cl) => (
|
||||
pub fn $name_mem(ctx: &mut JitContext, modrm_byte: ModrmByte, r: u32) {
|
||||
codegen::gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
codegen::gen_safe_read_write(ctx, BitSize::DWORD, &address_local, &|ref mut ctx| {
|
||||
codegen::gen_get_reg32(ctx, r);
|
||||
codegen::gen_get_reg8(ctx, regs::CL);
|
||||
ctx.builder.const_i32(31);
|
||||
ctx.builder.and_i32();
|
||||
ctx.builder.call_fn3_ret($fn);
|
||||
codegen::gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| {
|
||||
codegen::gen_safe_read_write(ctx, BitSize::DWORD, &addr, &|ref mut ctx| {
|
||||
codegen::gen_get_reg32(ctx, r);
|
||||
codegen::gen_get_reg8(ctx, regs::CL);
|
||||
ctx.builder.const_i32(31);
|
||||
ctx.builder.and_i32();
|
||||
ctx.builder.call_fn3_ret($fn);
|
||||
});
|
||||
});
|
||||
ctx.builder.free_local(address_local);
|
||||
}
|
||||
|
||||
pub fn $name_reg(ctx: &mut JitContext, r1: u32, r2: u32) {
|
||||
|
@ -858,15 +841,14 @@ macro_rules! define_instruction_read_write_mem32(
|
|||
|
||||
($fn:expr, $name_mem:ident, $name_reg:ident, reg, $imm:ident) => (
|
||||
pub fn $name_mem(ctx: &mut JitContext, modrm_byte: ModrmByte, r: u32, imm: u32) {
|
||||
codegen::gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
let imm = mask_imm!(imm, $imm) as i32;
|
||||
codegen::gen_safe_read_write(ctx, BitSize::DWORD, &address_local, &|ref mut ctx| {
|
||||
codegen::gen_get_reg32(ctx, r);
|
||||
ctx.builder.const_i32(imm as i32);
|
||||
ctx.builder.call_fn3_ret($fn);
|
||||
codegen::gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| {
|
||||
let imm = mask_imm!(imm, $imm) as i32;
|
||||
codegen::gen_safe_read_write(ctx, BitSize::DWORD, &addr, &|ref mut ctx| {
|
||||
codegen::gen_get_reg32(ctx, r);
|
||||
ctx.builder.const_i32(imm as i32);
|
||||
ctx.builder.call_fn3_ret($fn);
|
||||
});
|
||||
});
|
||||
ctx.builder.free_local(address_local);
|
||||
}
|
||||
|
||||
pub fn $name_reg(ctx: &mut JitContext, r1: u32, r2: u32, imm: u32) {
|
||||
|
@ -881,15 +863,14 @@ macro_rules! define_instruction_read_write_mem32(
|
|||
|
||||
($fn:expr, $name_mem:ident, $name_reg:ident, none) => (
|
||||
pub fn $name_mem(ctx: &mut JitContext, modrm_byte: ModrmByte) {
|
||||
codegen::gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
codegen::gen_safe_read_write(ctx, BitSize::DWORD, &address_local, &|ref mut ctx| {
|
||||
let mut dest_operand = ctx.builder.set_new_local();
|
||||
$fn(ctx, &mut dest_operand);
|
||||
ctx.builder.get_local(&dest_operand);
|
||||
ctx.builder.free_local(dest_operand);
|
||||
codegen::gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| {
|
||||
codegen::gen_safe_read_write(ctx, BitSize::DWORD, &addr, &|ref mut ctx| {
|
||||
let mut dest_operand = ctx.builder.set_new_local();
|
||||
$fn(ctx, &mut dest_operand);
|
||||
ctx.builder.get_local(&dest_operand);
|
||||
ctx.builder.free_local(dest_operand);
|
||||
});
|
||||
});
|
||||
ctx.builder.free_local(address_local);
|
||||
}
|
||||
|
||||
pub fn $name_reg(ctx: &mut JitContext, r1: u32) {
|
||||
|
@ -899,20 +880,19 @@ macro_rules! define_instruction_read_write_mem32(
|
|||
|
||||
($fn:expr, $name_mem:ident, $name_reg:ident, $imm:ident) => (
|
||||
pub fn $name_mem(ctx: &mut JitContext, modrm_byte: ModrmByte, imm: u32) {
|
||||
codegen::gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
let imm = mask_imm!(imm, $imm) as i32;
|
||||
codegen::gen_safe_read_write(ctx, BitSize::DWORD, &address_local, &|ref mut ctx| {
|
||||
let dest_operand = ctx.builder.set_new_local();
|
||||
$fn(
|
||||
ctx,
|
||||
&dest_operand,
|
||||
&LocalOrImmediate::Immediate(imm),
|
||||
);
|
||||
ctx.builder.get_local(&dest_operand);
|
||||
ctx.builder.free_local(dest_operand);
|
||||
codegen::gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| {
|
||||
let imm = mask_imm!(imm, $imm) as i32;
|
||||
codegen::gen_safe_read_write(ctx, BitSize::DWORD, &addr, &|ref mut ctx| {
|
||||
let dest_operand = ctx.builder.set_new_local();
|
||||
$fn(
|
||||
ctx,
|
||||
&dest_operand,
|
||||
&LocalOrImmediate::Immediate(imm),
|
||||
);
|
||||
ctx.builder.get_local(&dest_operand);
|
||||
ctx.builder.free_local(dest_operand);
|
||||
});
|
||||
});
|
||||
ctx.builder.free_local(address_local);
|
||||
}
|
||||
|
||||
pub fn $name_reg(ctx: &mut JitContext, r1: u32, imm: u32) {
|
||||
|
@ -2639,16 +2619,15 @@ define_instruction_read16!(gen_test16, instr16_85_mem_jit, instr16_85_reg_jit);
|
|||
define_instruction_read32!(gen_test32, instr32_85_mem_jit, instr32_85_reg_jit);
|
||||
|
||||
pub fn instr_86_mem_jit(ctx: &mut JitContext, modrm_byte: ModrmByte, r: u32) {
|
||||
codegen::gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
codegen::gen_safe_read_write(ctx, BitSize::BYTE, &address_local, &|ref mut ctx| {
|
||||
codegen::gen_get_reg8(ctx, r);
|
||||
let tmp = ctx.builder.set_new_local();
|
||||
codegen::gen_set_reg8(ctx, r);
|
||||
ctx.builder.get_local(&tmp);
|
||||
ctx.builder.free_local(tmp);
|
||||
codegen::gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| {
|
||||
codegen::gen_safe_read_write(ctx, BitSize::BYTE, &addr, &|ref mut ctx| {
|
||||
codegen::gen_get_reg8(ctx, r);
|
||||
let tmp = ctx.builder.set_new_local();
|
||||
codegen::gen_set_reg8(ctx, r);
|
||||
ctx.builder.get_local(&tmp);
|
||||
ctx.builder.free_local(tmp);
|
||||
});
|
||||
});
|
||||
ctx.builder.free_local(address_local);
|
||||
}
|
||||
pub fn instr_86_reg_jit(ctx: &mut JitContext, r1: u32, r2: u32) {
|
||||
codegen::gen_get_reg8(ctx, r2);
|
||||
|
@ -2660,28 +2639,26 @@ pub fn instr_86_reg_jit(ctx: &mut JitContext, r1: u32, r2: u32) {
|
|||
ctx.builder.free_local(tmp);
|
||||
}
|
||||
pub fn instr16_87_mem_jit(ctx: &mut JitContext, modrm_byte: ModrmByte, r: u32) {
|
||||
codegen::gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
codegen::gen_safe_read_write(ctx, BitSize::WORD, &address_local, &|ref mut ctx| {
|
||||
codegen::gen_get_reg16(ctx, r);
|
||||
let tmp = ctx.builder.set_new_local();
|
||||
codegen::gen_set_reg16(ctx, r);
|
||||
ctx.builder.get_local(&tmp);
|
||||
ctx.builder.free_local(tmp);
|
||||
codegen::gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| {
|
||||
codegen::gen_safe_read_write(ctx, BitSize::WORD, &addr, &|ref mut ctx| {
|
||||
codegen::gen_get_reg16(ctx, r);
|
||||
let tmp = ctx.builder.set_new_local();
|
||||
codegen::gen_set_reg16(ctx, r);
|
||||
ctx.builder.get_local(&tmp);
|
||||
ctx.builder.free_local(tmp);
|
||||
});
|
||||
});
|
||||
ctx.builder.free_local(address_local);
|
||||
}
|
||||
pub fn instr32_87_mem_jit(ctx: &mut JitContext, modrm_byte: ModrmByte, r: u32) {
|
||||
codegen::gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
codegen::gen_safe_read_write(ctx, BitSize::DWORD, &address_local, &|ref mut ctx| {
|
||||
codegen::gen_get_reg32(ctx, r);
|
||||
let tmp = ctx.builder.set_new_local();
|
||||
codegen::gen_set_reg32(ctx, r);
|
||||
ctx.builder.get_local(&tmp);
|
||||
ctx.builder.free_local(tmp);
|
||||
codegen::gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| {
|
||||
codegen::gen_safe_read_write(ctx, BitSize::DWORD, &addr, &|ref mut ctx| {
|
||||
codegen::gen_get_reg32(ctx, r);
|
||||
let tmp = ctx.builder.set_new_local();
|
||||
codegen::gen_set_reg32(ctx, r);
|
||||
ctx.builder.get_local(&tmp);
|
||||
ctx.builder.free_local(tmp);
|
||||
});
|
||||
});
|
||||
ctx.builder.free_local(address_local);
|
||||
}
|
||||
pub fn instr16_87_reg_jit(ctx: &mut JitContext, r1: u32, r2: u32) {
|
||||
codegen::gen_get_reg16(ctx, r2);
|
||||
|
@ -2703,36 +2680,29 @@ pub fn instr32_87_reg_jit(ctx: &mut JitContext, r1: u32, r2: u32) {
|
|||
}
|
||||
|
||||
pub fn instr_88_mem_jit(ctx: &mut JitContext, modrm_byte: ModrmByte, r: u32) {
|
||||
codegen::gen_modrm_resolve(ctx, modrm_byte);
|
||||
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
|
||||
codegen::gen_get_reg8(ctx, r);
|
||||
let value_local = ctx.builder.set_new_local();
|
||||
|
||||
codegen::gen_safe_write8(ctx, &address_local, &value_local);
|
||||
ctx.builder.free_local(address_local);
|
||||
ctx.builder.free_local(value_local);
|
||||
codegen::gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| {
|
||||
codegen::gen_get_reg8(ctx, r);
|
||||
let value_local = ctx.builder.set_new_local();
|
||||
codegen::gen_safe_write8(ctx, &addr, &value_local);
|
||||
ctx.builder.free_local(value_local);
|
||||
});
|
||||
}
|
||||
pub fn instr_88_reg_jit(ctx: &mut JitContext, r1: u32, r2: u32) {
|
||||
codegen::gen_set_reg8_r(ctx, r1, r2);
|
||||
}
|
||||
|
||||
pub fn instr16_89_mem_jit(ctx: &mut JitContext, modrm_byte: ModrmByte, r: u32) {
|
||||
codegen::gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
codegen::gen_safe_write16(ctx, &address_local, &ctx.reg(r));
|
||||
ctx.builder.free_local(address_local);
|
||||
codegen::gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| {
|
||||
codegen::gen_safe_write16(ctx, addr, &ctx.reg(r));
|
||||
});
|
||||
}
|
||||
pub fn instr16_89_reg_jit(ctx: &mut JitContext, r1: u32, r2: u32) {
|
||||
codegen::gen_set_reg16_r(ctx, r1, r2);
|
||||
}
|
||||
pub fn instr32_89_mem_jit(ctx: &mut JitContext, modrm_byte: ModrmByte, r: u32) {
|
||||
// Pseudo: safe_write32(modrm_resolve(modrm_byte), reg32[r]);
|
||||
codegen::gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
codegen::gen_safe_write32(ctx, &address_local, &ctx.reg(r));
|
||||
ctx.builder.free_local(address_local);
|
||||
codegen::gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| {
|
||||
codegen::gen_safe_write32(ctx, &addr, &ctx.reg(r));
|
||||
});
|
||||
}
|
||||
pub fn instr32_89_reg_jit(ctx: &mut JitContext, r1: u32, r2: u32) {
|
||||
codegen::gen_set_reg32_r(ctx, r1, r2);
|
||||
|
@ -2741,7 +2711,6 @@ pub fn instr32_89_reg_jit(ctx: &mut JitContext, r1: u32, r2: u32) {
|
|||
pub fn instr_8A_mem_jit(ctx: &mut JitContext, modrm_byte: ModrmByte, r: u32) {
|
||||
// Pseudo: reg8[r] = safe_read8(modrm_resolve(modrm_byte));
|
||||
codegen::gen_modrm_resolve_safe_read8(ctx, modrm_byte);
|
||||
|
||||
codegen::gen_set_reg8_unmasked(ctx, r);
|
||||
}
|
||||
pub fn instr_8A_reg_jit(ctx: &mut JitContext, r1: u32, r2: u32) {
|
||||
|
@ -5227,13 +5196,12 @@ pub fn instr_C6_0_reg_jit(ctx: &mut JitContext, r: u32, imm: u32) {
|
|||
}
|
||||
|
||||
pub fn instr_C6_0_mem_jit(ctx: &mut JitContext, modrm_byte: ModrmByte, imm: u32) {
|
||||
codegen::gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
ctx.builder.const_i32(imm as i32);
|
||||
let value_local = ctx.builder.set_new_local();
|
||||
codegen::gen_safe_write8(ctx, &address_local, &value_local);
|
||||
ctx.builder.free_local(address_local);
|
||||
ctx.builder.free_local(value_local);
|
||||
codegen::gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| {
|
||||
ctx.builder.const_i32(imm as i32);
|
||||
let value_local = ctx.builder.set_new_local();
|
||||
codegen::gen_safe_write8(ctx, &addr, &value_local);
|
||||
ctx.builder.free_local(value_local);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn instr16_C7_0_reg_jit(ctx: &mut JitContext, r: u32, imm: u32) {
|
||||
|
@ -5243,13 +5211,12 @@ pub fn instr16_C7_0_reg_jit(ctx: &mut JitContext, r: u32, imm: u32) {
|
|||
}
|
||||
|
||||
pub fn instr16_C7_0_mem_jit(ctx: &mut JitContext, modrm_byte: ModrmByte, imm: u32) {
|
||||
codegen::gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
ctx.builder.const_i32(imm as i32);
|
||||
let value_local = ctx.builder.set_new_local();
|
||||
codegen::gen_safe_write16(ctx, &address_local, &value_local);
|
||||
ctx.builder.free_local(address_local);
|
||||
ctx.builder.free_local(value_local);
|
||||
codegen::gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| {
|
||||
ctx.builder.const_i32(imm as i32);
|
||||
let value_local = ctx.builder.set_new_local();
|
||||
codegen::gen_safe_write16(ctx, &addr, &value_local);
|
||||
ctx.builder.free_local(value_local);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn instr32_C7_0_reg_jit(ctx: &mut JitContext, r: u32, imm: u32) {
|
||||
|
@ -5259,13 +5226,12 @@ pub fn instr32_C7_0_reg_jit(ctx: &mut JitContext, r: u32, imm: u32) {
|
|||
}
|
||||
|
||||
pub fn instr32_C7_0_mem_jit(ctx: &mut JitContext, modrm_byte: ModrmByte, imm: u32) {
|
||||
codegen::gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
ctx.builder.const_i32(imm as i32);
|
||||
let value_local = ctx.builder.set_new_local();
|
||||
codegen::gen_safe_write32(ctx, &address_local, &value_local);
|
||||
ctx.builder.free_local(address_local);
|
||||
ctx.builder.free_local(value_local);
|
||||
codegen::gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| {
|
||||
ctx.builder.const_i32(imm as i32);
|
||||
let value_local = ctx.builder.set_new_local();
|
||||
codegen::gen_safe_write32(ctx, &addr, &value_local);
|
||||
ctx.builder.free_local(value_local);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn instr_0FC8_jit(ctx: &mut JitContext) { gen_bswap(ctx, 0) }
|
||||
|
@ -5365,15 +5331,14 @@ define_cmovcc32!(0xF, instr32_0F4F_mem_jit, instr32_0F4F_reg_jit);
|
|||
macro_rules! define_setcc(
|
||||
($cond:expr, $name_mem:ident, $name_reg:ident) => (
|
||||
pub fn $name_mem(ctx: &mut JitContext, modrm_byte: ModrmByte, _r: u32) {
|
||||
codegen::gen_modrm_resolve(ctx, modrm_byte);
|
||||
let address_local = ctx.builder.set_new_local();
|
||||
codegen::gen_condition_fn(ctx, $cond);
|
||||
ctx.builder.const_i32(0);
|
||||
ctx.builder.ne_i32();
|
||||
let value_local = ctx.builder.set_new_local();
|
||||
codegen::gen_safe_write8(ctx, &address_local, &value_local);
|
||||
ctx.builder.free_local(address_local);
|
||||
ctx.builder.free_local(value_local);
|
||||
codegen::gen_modrm_resolve_with_local(ctx, modrm_byte, &|ctx, addr| {
|
||||
codegen::gen_condition_fn(ctx, $cond);
|
||||
ctx.builder.const_i32(0);
|
||||
ctx.builder.ne_i32();
|
||||
let value_local = ctx.builder.set_new_local();
|
||||
codegen::gen_safe_write8(ctx, &addr, &value_local);
|
||||
ctx.builder.free_local(value_local);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn $name_reg(ctx: &mut JitContext, r1: u32, _r2: u32) {
|
||||
|
|
|
@ -239,6 +239,22 @@ pub fn gen(ctx: &mut JitContext, modrm_byte: ModrmByte) {
|
|||
jit_add_seg_offset(ctx, modrm_byte.segment);
|
||||
}
|
||||
|
||||
pub fn get_as_reg_index_if_possible(ctx: &mut JitContext, modrm_byte: &ModrmByte) -> Option<u32> {
|
||||
let prefix = ctx.cpu.prefixes & PREFIX_MASK_SEGMENT;
|
||||
let seg = if prefix != 0 { prefix - 1 } else { modrm_byte.segment };
|
||||
if can_optimize_get_seg(ctx, seg)
|
||||
&& modrm_byte.second_reg.is_none()
|
||||
&& modrm_byte.immediate == 0
|
||||
&& !modrm_byte.is_16
|
||||
&& modrm_byte.shift == 0
|
||||
{
|
||||
modrm_byte.first_reg
|
||||
}
|
||||
else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn skip(ctx: &mut CpuContext, modrm_byte: u8) { let _ = decode(ctx, modrm_byte); }
|
||||
|
||||
#[derive(PartialEq)]
|
||||
|
|
Loading…
Reference in a new issue