#include "IR.h" #include #include "common/symbols.h" #include "goalc/emitter/IGen.h" #include "third-party/fmt/core.h" using namespace emitter; namespace { Register get_reg(const RegVal* rv, const AllocationResult& allocs, emitter::IR_Record irec) { if (rv->rlet_constraint().has_value()) { auto& range = allocs.ass_as_ranges; auto reg = rv->rlet_constraint().value(); if (rv->ireg().id < int(range.size())) { auto& lr = range.at(rv->ireg().id); if (lr.has_info_at(irec.ir_id)) { auto ass_reg = range.at(rv->ireg().id).get(irec.ir_id); if (ass_reg.kind == Assignment::Kind::REGISTER) { ASSERT(ass_reg.reg == reg); } else { ASSERT(false); } } else { ASSERT(false); } } else { ASSERT(false); } return reg; } else { auto& ass = allocs.ass_as_ranges.at(rv->ireg().id).get(irec.ir_id); ASSERT(ass.kind == Assignment::Kind::REGISTER); return ass.reg; } } int get_stack_offset(const RegVal* rv, const AllocationResult& allocs) { if (rv->rlet_constraint().has_value()) { // should be impossible. Can't take the address of an inline assembly form register. ASSERT(false); } else { ASSERT(rv->forced_on_stack()); auto& ass = allocs.ass_as_ranges.at(rv->ireg().id); auto stack_slot = allocs.get_slot_for_spill(ass.stack_slot()); ASSERT(stack_slot >= 0); return stack_slot * 8; } } Register get_no_color_reg(const RegVal* rv) { if (!rv->rlet_constraint().has_value()) { throw std::runtime_error( "Accessed a non-rlet constrained variable without the coloring system."); } return rv->rlet_constraint().value(); } Register get_reg_asm(const RegVal* rv, const AllocationResult& allocs, emitter::IR_Record irec, bool use_coloring) { return use_coloring ? get_reg(rv, allocs, irec) : get_no_color_reg(rv); } void load_constant(u64 value, emitter::ObjectGenerator* gen, emitter::IR_Record irec, Register dest_reg) { s64 svalue = value; if (svalue == 0) { gen->add_instr(IGen::xor_gpr64_gpr64(dest_reg, dest_reg), irec); } else if (svalue > 0) { if (svalue < UINT32_MAX) { gen->add_instr(IGen::mov_gpr64_u32(dest_reg, value), irec); } else { // need a real 64 bit load gen->add_instr(IGen::mov_gpr64_u64(dest_reg, value), irec); } } else { if (svalue >= INT32_MIN) { gen->add_instr(IGen::mov_gpr64_s32(dest_reg, svalue), irec); } else { // need a real 64 bit load gen->add_instr(IGen::mov_gpr64_u64(dest_reg, value), irec); } } } void regset_common(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec, const RegVal* dst, const RegVal* src, bool use_coloring) { auto src_reg = use_coloring ? get_reg(src, allocs, irec) : get_no_color_reg(src); auto dst_reg = use_coloring ? get_reg(dst, allocs, irec) : get_no_color_reg(dst); auto src_class = src->ireg().reg_class; auto dst_class = dst->ireg().reg_class; bool src_is_xmm128 = (src_class == RegClass::VECTOR_FLOAT || src_class == RegClass::INT_128); bool dst_is_xmm128 = (dst_class == RegClass::VECTOR_FLOAT || dst_class == RegClass::INT_128); if (src_class == RegClass::GPR_64 && dst_class == RegClass::GPR_64) { if (src_reg == dst_reg) { // eliminate move gen->count_eliminated_move(); gen->add_instr(IGen::null(), irec); } else { gen->add_instr(IGen::mov_gpr64_gpr64(dst_reg, src_reg), irec); } } else if (src_class == RegClass::FLOAT && dst_class == RegClass::FLOAT) { if (src_reg == dst_reg) { // eliminate move gen->count_eliminated_move(); gen->add_instr(IGen::null(), irec); } else { gen->add_instr(IGen::mov_xmm32_xmm32(dst_reg, src_reg), irec); } } else if (src_is_xmm128 && dst_is_xmm128) { if (src_reg == dst_reg) { // eliminate move gen->count_eliminated_move(); gen->add_instr(IGen::null(), irec); } else { gen->add_instr(IGen::mov_vf_vf(dst_reg, src_reg), irec); } } else if (src_class == RegClass::FLOAT && dst_class == RegClass::GPR_64) { // xmm 1x -> gpr gen->add_instr(IGen::movd_gpr32_xmm32(dst_reg, src_reg), irec); // don't forget to sign extend gen->add_instr(IGen::movsx_r64_r32(dst_reg, dst_reg), irec); } else if (src_class == RegClass::GPR_64 && dst_class == RegClass::FLOAT) { // gpr -> xmm 1x gen->add_instr(IGen::movd_xmm32_gpr32(dst_reg, src_reg), irec); } else if (src_is_xmm128 && dst_class == RegClass::FLOAT) { gen->add_instr(IGen::mov_xmm32_xmm32(dst_reg, src_reg), irec); } else if (src_class == RegClass::FLOAT && dst_is_xmm128) { gen->add_instr(IGen::mov_xmm32_xmm32(dst_reg, src_reg), irec); } else if (src_class == RegClass::GPR_64 && dst_is_xmm128) { gen->add_instr(IGen::movq_xmm64_gpr64(dst_reg, src_reg), irec); } else if (src_is_xmm128 && dst_class == RegClass::GPR_64) { gen->add_instr(IGen::movq_gpr64_xmm64(dst_reg, src_reg), irec); } else { ASSERT(false); // unhandled move. } } } // namespace /////////// // Return /////////// IR_Return::IR_Return(const RegVal* return_reg, const RegVal* value, emitter::Register ret_reg) : m_return_reg(return_reg), m_value(value), m_ret_reg(ret_reg) {} std::string IR_Return::print() { return fmt::format("ret {} {}", m_return_reg->print(), m_value->print()); } RegAllocInstr IR_Return::to_rai() { RegAllocInstr rai; rai.write.push_back(m_return_reg->ireg()); rai.read.push_back(m_value->ireg()); if (m_value->ireg().reg_class == m_return_reg->ireg().reg_class) { rai.is_move = true; // only true if we aren't moving from register kind to register kind } return rai; } void IR_Return::add_constraints(std::vector* constraints, int my_id) { IRegConstraint c; if (dynamic_cast(m_return_reg)) { return; } c.ireg = m_return_reg->ireg(); c.instr_idx = my_id; c.desired_register = m_ret_reg; constraints->push_back(c); } void IR_Return::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { auto val_reg = get_reg(m_value, allocs, irec); auto dest_reg = get_reg(m_return_reg, allocs, irec); if (val_reg == dest_reg) { gen->add_instr(IGen::null(), irec); } else { regset_common(gen, allocs, irec, m_return_reg, m_value, true); // gen->add_instr(IGen::mov_gpr64_gpr64(dest_reg, val_reg), irec); } } ///////////////////// // LoadConstant64 ///////////////////// IR_LoadConstant64::IR_LoadConstant64(const RegVal* dest, u64 value) : m_dest(dest), m_value(value) {} std::string IR_LoadConstant64::print() { return fmt::format("mov-ic {}, {}", m_dest->print(), m_value); } RegAllocInstr IR_LoadConstant64::to_rai() { RegAllocInstr rai; rai.write.push_back(m_dest->ireg()); return rai; } void IR_LoadConstant64::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { auto dest_reg = get_reg(m_dest, allocs, irec); load_constant(m_value, gen, irec, dest_reg); } ///////////////////// // LoadSymbolPointer ///////////////////// IR_LoadSymbolPointer::IR_LoadSymbolPointer(const RegVal* dest, std::string name) : m_dest(dest), m_name(std::move(name)) {} std::string IR_LoadSymbolPointer::print() { return fmt::format("mov-symptr {}, '{}", m_dest->print(), m_name); } RegAllocInstr IR_LoadSymbolPointer::to_rai() { RegAllocInstr rai; rai.write.push_back(m_dest->ireg()); return rai; } void IR_LoadSymbolPointer::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { auto dest_reg = get_reg(m_dest, allocs, irec); if (m_name == "#f") { static_assert(false_symbol_offset() == 0, "false symbol location"); gen->add_instr(IGen::mov_gpr64_gpr64(dest_reg, gRegInfo.get_st_reg()), irec); } else if (m_name == "#t") { gen->add_instr(IGen::lea_reg_plus_off8(dest_reg, gRegInfo.get_st_reg(), true_symbol_offset(gen->version())), irec); } else if (m_name == "_empty_") { gen->add_instr(IGen::lea_reg_plus_off8(dest_reg, gRegInfo.get_st_reg(), empty_pair_offset_from_s7(gen->version())), irec); } else { auto instr = gen->add_instr(IGen::lea_reg_plus_off32(dest_reg, gRegInfo.get_st_reg(), 0x0afecafe), irec); gen->link_instruction_symbol_ptr(instr, m_name); } } ///////////////////// // SetSymbolValue ///////////////////// IR_SetSymbolValue::IR_SetSymbolValue(const SymbolVal* dest, const RegVal* src) : m_dest(dest), m_src(src) {} std::string IR_SetSymbolValue::print() { return fmt::format("mov '{}, {}", m_dest->name(), m_src->print()); } RegAllocInstr IR_SetSymbolValue::to_rai() { RegAllocInstr rai; rai.read.push_back(m_src->ireg()); return rai; } void IR_SetSymbolValue::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { auto src_reg = get_reg(m_src, allocs, irec); auto instr = gen->add_instr( IGen::store32_gpr64_gpr64_plus_gpr64_plus_s32( gRegInfo.get_st_reg(), gRegInfo.get_offset_reg(), src_reg, LINK_SYM_NO_OFFSET_FLAG), irec); gen->link_instruction_symbol_mem(instr, m_dest->name()); } ///////////////////// // GetSymbolValue ///////////////////// IR_GetSymbolValue::IR_GetSymbolValue(const RegVal* dest, const SymbolVal* src, bool sext) : m_dest(dest), m_src(src), m_sext(sext) {} std::string IR_GetSymbolValue::print() { return fmt::format("mov {}, '{}", m_dest->print(), m_src->name()); } RegAllocInstr IR_GetSymbolValue::to_rai() { RegAllocInstr rai; rai.write.push_back(m_dest->ireg()); return rai; } void IR_GetSymbolValue::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { auto dst_reg = get_reg(m_dest, allocs, irec); if (m_sext) { auto instr = gen->add_instr( IGen::load32s_gpr64_gpr64_plus_gpr64_plus_s32( dst_reg, gRegInfo.get_st_reg(), gRegInfo.get_offset_reg(), LINK_SYM_NO_OFFSET_FLAG), irec); gen->link_instruction_symbol_mem(instr, m_src->name()); } else { auto instr = gen->add_instr( IGen::load32u_gpr64_gpr64_plus_gpr64_plus_s32( dst_reg, gRegInfo.get_st_reg(), gRegInfo.get_offset_reg(), LINK_SYM_NO_OFFSET_FLAG), irec); gen->link_instruction_symbol_mem(instr, m_src->name()); } } ///////////////////// // RegSet ///////////////////// IR_RegSet::IR_RegSet(const RegVal* dest, const RegVal* src) : m_dest(dest), m_src(src) {} RegAllocInstr IR_RegSet::to_rai() { RegAllocInstr rai; rai.write.push_back(m_dest->ireg()); rai.read.push_back(m_src->ireg()); if (m_dest->ireg().reg_class == m_src->ireg().reg_class) { rai.is_move = true; // only true if we aren't moving from register kind to register kind } return rai; } void IR_RegSet::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { regset_common(gen, allocs, irec, m_dest, m_src, true); } std::string IR_RegSet::print() { return fmt::format("mov {}, {}", m_dest->print(), m_src->print()); } ///////////////////// // GotoLabel ///////////////////// IR_GotoLabel::IR_GotoLabel(const Label* dest) : m_dest(dest) { m_resolved = true; } IR_GotoLabel::IR_GotoLabel() { m_resolved = false; } std::string IR_GotoLabel::print() { return fmt::format("goto {}", m_dest->print()); } RegAllocInstr IR_GotoLabel::to_rai() { ASSERT(m_resolved); RegAllocInstr rai; rai.jumps.push_back(m_dest->idx); rai.fallthrough = false; return rai; } void IR_GotoLabel::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { (void)allocs; auto instr = gen->add_instr(IGen::jmp_32(), irec); gen->link_instruction_jump(instr, gen->get_future_ir_record_in_same_func(irec, m_dest->idx)); } void IR_GotoLabel::resolve(const Label* dest) { ASSERT(!m_resolved); m_dest = dest; m_resolved = true; } ///////////////////// // FunctionCall ///////////////////// IR_FunctionCall::IR_FunctionCall(const RegVal* func, const RegVal* ret, std::vector args, std::vector arg_regs, std::optional ret_reg) : m_func(func), m_ret(ret), m_args(std::move(args)), m_arg_regs(std::move(arg_regs)), m_ret_reg(ret_reg) {} std::string IR_FunctionCall::print() { std::string result = fmt::format("call {} (ret {}) (args ", m_func->print(), m_ret->print()); for (const auto& x : m_args) { result += fmt::format("{} ", x->print()); } result.pop_back(); result.push_back(')'); return result; } RegAllocInstr IR_FunctionCall::to_rai() { RegAllocInstr rai; rai.read.push_back(m_func->ireg()); rai.write.push_back(m_func->ireg()); // todo, can we avoid this? rai.write.push_back(m_ret->ireg()); for (auto& arg : m_args) { rai.read.push_back(arg->ireg()); } for (int i = 0; i < emitter::RegisterInfo::N_REGS; i++) { auto info = emitter::gRegInfo.get_info(i); if (info.temp()) { rai.clobber.emplace_back(i); } } return rai; } void IR_FunctionCall::add_constraints(std::vector* constraints, int my_id) { for (size_t i = 0; i < m_args.size(); i++) { IRegConstraint c; c.ireg = m_args.at(i)->ireg(); c.instr_idx = my_id; c.desired_register = m_arg_regs.at(i); constraints->push_back(c); } if (m_ret_reg) { IRegConstraint c; c.ireg = m_ret->ireg(); c.desired_register = *m_ret_reg; c.instr_idx = my_id; constraints->push_back(c); } } void IR_FunctionCall::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { auto freg = get_reg(m_func, allocs, irec); gen->add_instr(IGen::add_gpr64_gpr64(freg, emitter::gRegInfo.get_offset_reg()), irec); gen->add_instr(IGen::call_r64(freg), irec); // todo, can we do a sub to undo the modification to the register? does that actually work? } ///////////////////// // RegValAddr ///////////////////// IR_RegValAddr::IR_RegValAddr(const RegVal* dest, const RegVal* src) : m_dest(dest), m_src(src) {} std::string IR_RegValAddr::print() { return fmt::format("mov {}, &{}", m_dest->print(), m_src->print()); } RegAllocInstr IR_RegValAddr::to_rai() { RegAllocInstr rai; rai.write.push_back(m_dest->ireg()); // we don't actually read the value in m_src, so we don't need to add it here. return rai; } void IR_RegValAddr::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { int stack_offset = get_stack_offset(m_src, allocs); auto dst = get_reg(m_dest, allocs, irec); // x86 pointer to var gen->add_instr(IGen::lea_reg_plus_off(dst, RSP, stack_offset), irec); // x86 -> GOAL pointer gen->add_instr(IGen::sub_gpr64_gpr64(dst, emitter::gRegInfo.get_offset_reg()), irec); } ///////////////////// // StaticVarAddr ///////////////////// IR_StaticVarAddr::IR_StaticVarAddr(const RegVal* dest, const StaticObject* src) : m_dest(dest), m_src(src) {} std::string IR_StaticVarAddr::print() { return fmt::format("mov-sva {}, {}", m_dest->print(), m_src->print()); } RegAllocInstr IR_StaticVarAddr::to_rai() { RegAllocInstr rai; rai.write.push_back(m_dest->ireg()); return rai; } void IR_StaticVarAddr::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { auto dr = get_reg(m_dest, allocs, irec); auto instr = gen->add_instr(IGen::static_addr(dr, 0), irec); gen->link_instruction_static(instr, m_src->rec, m_src->get_addr_offset()); gen->add_instr(IGen::sub_gpr64_gpr64(dr, emitter::gRegInfo.get_offset_reg()), irec); } ///////////////////// // FunctionAddr ///////////////////// IR_FunctionAddr::IR_FunctionAddr(const RegVal* dest, FunctionEnv* src) : m_dest(dest), m_src(src) {} std::string IR_FunctionAddr::print() { return fmt::format("mov-fa {}, {}", m_dest->print(), m_src->print()); } RegAllocInstr IR_FunctionAddr::to_rai() { RegAllocInstr rai; rai.write.push_back(m_dest->ireg()); return rai; } void IR_FunctionAddr::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { auto dr = get_reg(m_dest, allocs, irec); auto instr = gen->add_instr(IGen::static_addr(dr, 0), irec); gen->link_instruction_to_function(instr, gen->get_existing_function_record(m_src->idx_in_file)); gen->add_instr(IGen::sub_gpr64_gpr64(dr, emitter::gRegInfo.get_offset_reg()), irec); } ///////////////////// // IntegerMath ///////////////////// IR_IntegerMath::IR_IntegerMath(IntegerMathKind kind, RegVal* dest, RegVal* arg) : m_kind(kind), m_dest(dest), m_arg(arg) {} IR_IntegerMath::IR_IntegerMath(IntegerMathKind kind, RegVal* dest, u8 shift_amount) : m_kind(kind), m_dest(dest), m_shift_amount(shift_amount) {} std::string IR_IntegerMath::print() { switch (m_kind) { case IntegerMathKind::ADD_64: return fmt::format("addi {}, {}", m_dest->print(), m_arg->print()); case IntegerMathKind::SUB_64: return fmt::format("subi {}, {}", m_dest->print(), m_arg->print()); case IntegerMathKind::IMUL_32: return fmt::format("imul {}, {}", m_dest->print(), m_arg->print()); case IntegerMathKind::IMUL_64: return fmt::format("imul64 {}, {}", m_dest->print(), m_arg->print()); case IntegerMathKind::IDIV_32: return fmt::format("idiv {}, {}", m_dest->print(), m_arg->print()); case IntegerMathKind::UDIV_32: return fmt::format("udiv {}, {}", m_dest->print(), m_arg->print()); case IntegerMathKind::IMOD_32: return fmt::format("imod {}, {}", m_dest->print(), m_arg->print()); case IntegerMathKind::UMOD_32: return fmt::format("umod {}, {}", m_dest->print(), m_arg->print()); case IntegerMathKind::SARV_64: return fmt::format("sarv {}, {}", m_dest->print(), m_arg->print()); case IntegerMathKind::SHLV_64: return fmt::format("shlv {}, {}", m_dest->print(), m_arg->print()); case IntegerMathKind::SHRV_64: return fmt::format("shrv {}, {}", m_dest->print(), m_arg->print()); case IntegerMathKind::SAR_64: return fmt::format("sar {}, {}", m_dest->print(), m_shift_amount); case IntegerMathKind::SHL_64: return fmt::format("shl {}, {}", m_dest->print(), m_shift_amount); case IntegerMathKind::SHR_64: return fmt::format("shr {}, {}", m_dest->print(), m_shift_amount); case IntegerMathKind::AND_64: return fmt::format("and {}, {}", m_dest->print(), m_arg->print()); case IntegerMathKind::OR_64: return fmt::format("or {}, {}", m_dest->print(), m_arg->print()); case IntegerMathKind::XOR_64: return fmt::format("xor {}, {}", m_dest->print(), m_arg->print()); case IntegerMathKind::NOT_64: return fmt::format("not {}", m_dest->print()); default: throw std::runtime_error("Unsupported IntegerMathKind"); } } RegAllocInstr IR_IntegerMath::to_rai() { RegAllocInstr rai; rai.write.push_back(m_dest->ireg()); rai.read.push_back(m_dest->ireg()); if (m_kind != IntegerMathKind::NOT_64 && m_kind != IntegerMathKind::SHL_64 && m_kind != IntegerMathKind::SAR_64 && m_kind != IntegerMathKind::SHR_64) { rai.read.push_back(m_arg->ireg()); } if (m_kind == IntegerMathKind::IDIV_32 || m_kind == IntegerMathKind::IMOD_32 || m_kind == IntegerMathKind::UDIV_32 || m_kind == IntegerMathKind::UMOD_32) { rai.exclude.emplace_back(emitter::RDX); } return rai; } void IR_IntegerMath::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { switch (m_kind) { case IntegerMathKind::ADD_64: gen->add_instr( IGen::add_gpr64_gpr64(get_reg(m_dest, allocs, irec), get_reg(m_arg, allocs, irec)), irec); break; case IntegerMathKind::SUB_64: gen->add_instr( IGen::sub_gpr64_gpr64(get_reg(m_dest, allocs, irec), get_reg(m_arg, allocs, irec)), irec); break; case IntegerMathKind::AND_64: gen->add_instr( IGen::and_gpr64_gpr64(get_reg(m_dest, allocs, irec), get_reg(m_arg, allocs, irec)), irec); break; case IntegerMathKind::OR_64: gen->add_instr( IGen::or_gpr64_gpr64(get_reg(m_dest, allocs, irec), get_reg(m_arg, allocs, irec)), irec); break; case IntegerMathKind::XOR_64: gen->add_instr( IGen::xor_gpr64_gpr64(get_reg(m_dest, allocs, irec), get_reg(m_arg, allocs, irec)), irec); break; case IntegerMathKind::NOT_64: gen->add_instr(IGen::not_gpr64(get_reg(m_dest, allocs, irec)), irec); ASSERT(!m_arg); break; case IntegerMathKind::SHLV_64: gen->add_instr(IGen::shl_gpr64_cl(get_reg(m_dest, allocs, irec)), irec); ASSERT(get_reg(m_arg, allocs, irec) == emitter::RCX); break; case IntegerMathKind::SHRV_64: gen->add_instr(IGen::shr_gpr64_cl(get_reg(m_dest, allocs, irec)), irec); ASSERT(get_reg(m_arg, allocs, irec) == emitter::RCX); break; case IntegerMathKind::SARV_64: gen->add_instr(IGen::sar_gpr64_cl(get_reg(m_dest, allocs, irec)), irec); ASSERT(get_reg(m_arg, allocs, irec) == emitter::RCX); break; case IntegerMathKind::SHL_64: gen->add_instr(IGen::shl_gpr64_u8(get_reg(m_dest, allocs, irec), m_shift_amount), irec); break; case IntegerMathKind::SHR_64: gen->add_instr(IGen::shr_gpr64_u8(get_reg(m_dest, allocs, irec), m_shift_amount), irec); break; case IntegerMathKind::SAR_64: gen->add_instr(IGen::sar_gpr64_u8(get_reg(m_dest, allocs, irec), m_shift_amount), irec); break; case IntegerMathKind::IMUL_32: { // just a 32-bit multiply, signed/unsigned doesn't affect lower 32 bits of result. auto dr = get_reg(m_dest, allocs, irec); gen->add_instr(IGen::imul_gpr32_gpr32(dr, get_reg(m_arg, allocs, irec)), irec); // the PS2 sign extends the result even if we used multu. We replicate this here. gen->add_instr(IGen::movsx_r64_r32(dr, dr), irec); } break; case IntegerMathKind::IMUL_64: { auto dr = get_reg(m_dest, allocs, irec); gen->add_instr(IGen::imul_gpr64_gpr64(dr, get_reg(m_arg, allocs, irec)), irec); } break; case IntegerMathKind::IDIV_32: { gen->add_instr(IGen::cdq(), irec); gen->add_instr(IGen::idiv_gpr32(get_reg(m_arg, allocs, irec)), irec); gen->add_instr(IGen::movsx_r64_r32(get_reg(m_dest, allocs, irec), emitter::RAX), irec); } break; case IntegerMathKind::UDIV_32: { // zero extend, not sign extend to avoid overflow gen->add_instr(IGen::xor_gpr64_gpr64(Register(RDX), Register(RDX)), irec); gen->add_instr(IGen::unsigned_div_gpr32(get_reg(m_arg, allocs, irec)), irec); // note: this probably needs hardware testing to know for sure if the PS2 actually sign // extends here or not. Nothing seems to break either way, and PCSX2/Dobie interpreters both // sign extend, so that seems like the safest option. gen->add_instr(IGen::movsx_r64_r32(get_reg(m_dest, allocs, irec), emitter::RAX), irec); } break; case IntegerMathKind::IMOD_32: { gen->add_instr(IGen::cdq(), irec); gen->add_instr(IGen::idiv_gpr32(get_reg(m_arg, allocs, irec)), irec); gen->add_instr(IGen::movsx_r64_r32(get_reg(m_dest, allocs, irec), emitter::RDX), irec); } break; case IntegerMathKind::UMOD_32: { // zero extend, not sign extend to avoid overflow gen->add_instr(IGen::xor_gpr64_gpr64(Register(RDX), Register(RDX)), irec); gen->add_instr(IGen::unsigned_div_gpr32(get_reg(m_arg, allocs, irec)), irec); // see note on udiv, same applies here. gen->add_instr(IGen::movsx_r64_r32(get_reg(m_dest, allocs, irec), emitter::RDX), irec); } break; default: ASSERT(false); } } ///////////////////// // FloatMath ///////////////////// IR_FloatMath::IR_FloatMath(FloatMathKind kind, RegVal* dest, RegVal* arg) : m_kind(kind), m_dest(dest), m_arg(arg) {} std::string IR_FloatMath::print() { switch (m_kind) { case FloatMathKind::DIV_SS: return fmt::format("divss {}, {}", m_dest->print(), m_arg->print()); case FloatMathKind::MUL_SS: return fmt::format("mulss {}, {}", m_dest->print(), m_arg->print()); case FloatMathKind::ADD_SS: return fmt::format("addss {}, {}", m_dest->print(), m_arg->print()); case FloatMathKind::SUB_SS: return fmt::format("subss {}, {}", m_dest->print(), m_arg->print()); case FloatMathKind::MAX_SS: return fmt::format("maxss {}, {}", m_dest->print(), m_arg->print()); case FloatMathKind::MIN_SS: return fmt::format("minss {}, {}", m_dest->print(), m_arg->print()); case FloatMathKind::SQRT_SS: return fmt::format("sqrtss {}, {}", m_dest->print(), m_arg->print()); default: throw std::runtime_error("Unsupported FloatMathKind"); } } RegAllocInstr IR_FloatMath::to_rai() { RegAllocInstr rai; rai.write.push_back(m_dest->ireg()); if (m_kind != FloatMathKind::SQRT_SS) { rai.read.push_back(m_dest->ireg()); } rai.read.push_back(m_arg->ireg()); return rai; } void IR_FloatMath::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { switch (m_kind) { case FloatMathKind::DIV_SS: gen->add_instr( IGen::divss_xmm_xmm(get_reg(m_dest, allocs, irec), get_reg(m_arg, allocs, irec)), irec); break; case FloatMathKind::MUL_SS: gen->add_instr( IGen::mulss_xmm_xmm(get_reg(m_dest, allocs, irec), get_reg(m_arg, allocs, irec)), irec); break; case FloatMathKind::ADD_SS: gen->add_instr( IGen::addss_xmm_xmm(get_reg(m_dest, allocs, irec), get_reg(m_arg, allocs, irec)), irec); break; case FloatMathKind::SUB_SS: gen->add_instr( IGen::subss_xmm_xmm(get_reg(m_dest, allocs, irec), get_reg(m_arg, allocs, irec)), irec); break; case FloatMathKind::MAX_SS: gen->add_instr( IGen::maxss_xmm_xmm(get_reg(m_dest, allocs, irec), get_reg(m_arg, allocs, irec)), irec); break; case FloatMathKind::MIN_SS: gen->add_instr( IGen::minss_xmm_xmm(get_reg(m_dest, allocs, irec), get_reg(m_arg, allocs, irec)), irec); break; case FloatMathKind::SQRT_SS: gen->add_instr(IGen::sqrts_xmm(get_reg(m_dest, allocs, irec), get_reg(m_arg, allocs, irec)), irec); break; default: ASSERT(false); } } ///////////////////// // StaticVarLoad ///////////////////// IR_StaticVarLoad::IR_StaticVarLoad(const RegVal* dest, const StaticObject* src) : m_dest(dest), m_src(src) {} std::string IR_StaticVarLoad::print() { return fmt::format("mov-svl {}, [{}]", m_dest->print(), m_src->print()); } RegAllocInstr IR_StaticVarLoad::to_rai() { RegAllocInstr rai; rai.write.push_back(m_dest->ireg()); return rai; } void IR_StaticVarLoad::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { auto load_info = m_src->get_load_info(); ASSERT(m_src->get_addr_offset() == 0); if (m_dest->ireg().reg_class == RegClass::FLOAT) { ASSERT(load_info.load_signed == false); ASSERT(load_info.load_size == 4); ASSERT(load_info.requires_load == true); auto instr = gen->add_instr(IGen::static_load_xmm32(get_reg(m_dest, allocs, irec), 0), irec); gen->link_instruction_static(instr, m_src->rec, 0); } else if (m_dest->ireg().reg_class == RegClass::VECTOR_FLOAT) { // we don't check the load info intentionally because we want to allow loading an entire // vector structure. auto instr = gen->add_instr(IGen::loadvf_rip_plus_s32(get_reg(m_dest, allocs, irec), 0), irec); gen->link_instruction_static(instr, m_src->rec, 0); } else { ASSERT(false); } } ///////////////////// // ConditionalBranch ///////////////////// std::string Condition::print() const { switch (kind) { case ConditionKind::NOT_EQUAL: return a->print() + " != " + b->print(); case ConditionKind::EQUAL: return a->print() + " == " + b->print(); case ConditionKind::LEQ: return a->print() + " <= " + b->print(); case ConditionKind::GEQ: return a->print() + " >= " + b->print(); case ConditionKind::LT: return a->print() + " < " + b->print(); case ConditionKind::GT: return a->print() + " > " + b->print(); default: throw std::runtime_error("unknown condition type in GoalCondition::print()"); } } RegAllocInstr Condition::to_rai() { RegAllocInstr rai; rai.read.push_back(a->ireg()); rai.read.push_back(b->ireg()); return rai; } IR_ConditionalBranch::IR_ConditionalBranch(const Condition& _condition, Label _label) : condition(_condition), label(_label) {} std::string IR_ConditionalBranch::print() { // todo, float/signed info? return fmt::format("j({}) {}", condition.print(), label.print()); } RegAllocInstr IR_ConditionalBranch::to_rai() { auto rai = condition.to_rai(); ASSERT(m_resolved); rai.jumps.push_back(label.idx); return rai; } void IR_ConditionalBranch::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { Instruction jump_instr(0); ASSERT(m_resolved); switch (condition.kind) { case ConditionKind::EQUAL: jump_instr = IGen::je_32(); break; case ConditionKind::NOT_EQUAL: jump_instr = IGen::jne_32(); break; case ConditionKind::LEQ: if (condition.is_signed) { jump_instr = IGen::jle_32(); } else { jump_instr = IGen::jbe_32(); } break; case ConditionKind::GEQ: if (condition.is_signed) { jump_instr = IGen::jge_32(); } else { jump_instr = IGen::jae_32(); } break; case ConditionKind::LT: if (condition.is_signed) { jump_instr = IGen::jl_32(); } else { jump_instr = IGen::jb_32(); } break; case ConditionKind::GT: if (condition.is_signed) { jump_instr = IGen::jg_32(); } else { jump_instr = IGen::ja_32(); } break; default: ASSERT(false); } if (condition.is_float) { gen->add_instr( IGen::cmp_flt_flt(get_reg(condition.a, allocs, irec), get_reg(condition.b, allocs, irec)), irec); } else { gen->add_instr(IGen::cmp_gpr64_gpr64(get_reg(condition.a, allocs, irec), get_reg(condition.b, allocs, irec)), irec); } auto jump_rec = gen->add_instr(jump_instr, irec); gen->link_instruction_jump(jump_rec, gen->get_future_ir_record_in_same_func(irec, label.idx)); } ///////////////////// // LoadConstantOffset ///////////////////// IR_LoadConstOffset::IR_LoadConstOffset(const RegVal* dest, int offset, const RegVal* base, MemLoadInfo info, bool use_coloring) : IR_Asm(use_coloring), m_dest(dest), m_offset(offset), m_base(base), m_info(info) {} std::string IR_LoadConstOffset::print() { return fmt::format("mov {}, [{} + {}]", m_dest->print(), m_base->print(), m_offset); } RegAllocInstr IR_LoadConstOffset::to_rai() { RegAllocInstr rai; rai.write.push_back(m_dest->ireg()); rai.read.push_back(m_base->ireg()); return rai; } void IR_LoadConstOffset::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { auto dest_reg = m_use_coloring ? get_reg(m_dest, allocs, irec) : get_no_color_reg(m_dest); auto base_reg = m_use_coloring ? get_reg(m_base, allocs, irec) : get_no_color_reg(m_base); if (m_dest->ireg().reg_class == RegClass::GPR_64) { gen->add_instr(IGen::load_goal_gpr(dest_reg, base_reg, emitter::gRegInfo.get_offset_reg(), m_offset, m_info.size, m_info.sign_extend), irec); } else if (m_dest->ireg().reg_class == RegClass::FLOAT && m_info.size == 4 && m_info.sign_extend == false && m_info.reg == RegClass::FLOAT) { gen->add_instr( IGen::load_goal_xmm32(dest_reg, base_reg, emitter::gRegInfo.get_offset_reg(), m_offset), irec); } else if ((m_dest->ireg().reg_class == RegClass::VECTOR_FLOAT || m_dest->ireg().reg_class == RegClass::INT_128) && m_info.size == 16 && m_info.sign_extend == false && m_info.reg == m_dest->ireg().reg_class) { gen->add_instr( IGen::load_goal_xmm128(dest_reg, base_reg, emitter::gRegInfo.get_offset_reg(), m_offset), irec); } else { throw std::runtime_error("IR_LoadConstOffset::do_codegen not supported"); } } /////////////////////// // StoreConstantOffset /////////////////////// IR_StoreConstOffset::IR_StoreConstOffset(const RegVal* value, int offset, const RegVal* base, int size, bool use_coloring) : IR_Asm(use_coloring), m_value(value), m_offset(offset), m_base(base), m_size(size) {} std::string IR_StoreConstOffset::print() { return fmt::format("move [{} + {}], {}", m_base->print(), m_offset, m_value->print()); } RegAllocInstr IR_StoreConstOffset::to_rai() { RegAllocInstr rai; rai.read.push_back(m_value->ireg()); rai.read.push_back(m_base->ireg()); return rai; } void IR_StoreConstOffset::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { auto base_reg = m_use_coloring ? get_reg(m_base, allocs, irec) : get_no_color_reg(m_base); auto value_reg = m_use_coloring ? get_reg(m_value, allocs, irec) : get_no_color_reg(m_value); if (m_value->ireg().reg_class == RegClass::GPR_64) { gen->add_instr(IGen::store_goal_gpr(base_reg, value_reg, emitter::gRegInfo.get_offset_reg(), m_offset, m_size), irec); } else if (m_value->ireg().reg_class == RegClass::FLOAT && m_size == 4) { gen->add_instr( IGen::store_goal_xmm32(base_reg, value_reg, emitter::gRegInfo.get_offset_reg(), m_offset), irec); } else if ((m_value->ireg().reg_class == RegClass::VECTOR_FLOAT || m_value->ireg().reg_class == RegClass::INT_128) && m_size == 16) { gen->add_instr( IGen::store_goal_vf(base_reg, value_reg, emitter::gRegInfo.get_offset_reg(), m_offset), irec); } else { throw std::runtime_error( fmt::format("IR_StoreConstOffset::do_codegen can't handle this (c {} sz {})", m_value->ireg().reg_class, m_size)); } } /////////////////////// // Null /////////////////////// std::string IR_Null::print() { return "null"; } RegAllocInstr IR_Null::to_rai() { return {}; } void IR_Null::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { (void)gen; (void)allocs; (void)irec; } /////////////////////// // ValueReset /////////////////////// IR_ValueReset::IR_ValueReset(std::vector args) : m_args(std::move(args)) {} std::string IR_ValueReset::print() { return "value-reset"; } RegAllocInstr IR_ValueReset::to_rai() { RegAllocInstr rai; for (auto& x : m_args) { rai.write.push_back(x->ireg()); } return rai; } void IR_ValueReset::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { (void)gen; (void)allocs; (void)irec; } /////////////////////// // FloatToInt /////////////////////// IR_FloatToInt::IR_FloatToInt(const RegVal* dest, const RegVal* src) : m_dest(dest), m_src(src) {} std::string IR_FloatToInt::print() { return fmt::format("f2i {}, {}", m_dest->print(), m_src->print()); } RegAllocInstr IR_FloatToInt::to_rai() { RegAllocInstr rai; rai.read.push_back(m_src->ireg()); rai.write.push_back(m_dest->ireg()); return rai; } void IR_FloatToInt::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { gen->add_instr(IGen::float_to_int32(get_reg(m_dest, allocs, irec), get_reg(m_src, allocs, irec)), irec); gen->add_instr(IGen::movsx_r64_r32(get_reg(m_dest, allocs, irec), get_reg(m_dest, allocs, irec)), irec); } /////////////////////// // IntToFloat /////////////////////// IR_IntToFloat::IR_IntToFloat(const RegVal* dest, const RegVal* src) : m_dest(dest), m_src(src) {} std::string IR_IntToFloat::print() { return fmt::format("i2f {}, {}", m_dest->print(), m_src->print()); } RegAllocInstr IR_IntToFloat::to_rai() { RegAllocInstr rai; rai.read.push_back(m_src->ireg()); rai.write.push_back(m_dest->ireg()); return rai; } void IR_IntToFloat::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { gen->add_instr(IGen::int32_to_float(get_reg(m_dest, allocs, irec), get_reg(m_src, allocs, irec)), irec); } /////////////////////// // GetStackAddr /////////////////////// IR_GetStackAddr::IR_GetStackAddr(const RegVal* dest, int slot) : m_dest(dest), m_slot(slot) {} std::string IR_GetStackAddr::print() { return fmt::format("mov {}, stack-slot-{}", m_dest->print(), m_slot); } RegAllocInstr IR_GetStackAddr::to_rai() { RegAllocInstr rai; rai.write.push_back(m_dest->ireg()); return rai; } void IR_GetStackAddr::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { auto dest_reg = get_reg(m_dest, allocs, irec); int offset = GPR_SIZE * allocs.get_slot_for_var(m_slot); if (offset == 0) { gen->add_instr(IGen::mov_gpr64_gpr64(dest_reg, RSP), irec); gen->add_instr(IGen::sub_gpr64_gpr64(dest_reg, gRegInfo.get_offset_reg()), irec); } else { // dest = offset + RSP gen->add_instr(IGen::lea_reg_plus_off(dest_reg, RSP, offset), irec); // dest = offset + RSP - offset gen->add_instr(IGen::sub_gpr64_gpr64(dest_reg, gRegInfo.get_offset_reg()), irec); } } /////////////////////// // Nop /////////////////////// IR_Nop::IR_Nop() {} std::string IR_Nop::print() { return fmt::format("nop"); } RegAllocInstr IR_Nop::to_rai() { return {}; } void IR_Nop::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult&, emitter::IR_Record irec) { gen->add_instr(IGen::nop(), irec); } /////////////////////// // Asm /////////////////////// IR_Asm::IR_Asm(bool use_coloring) : m_use_coloring(use_coloring) {} std::string IR_Asm::get_color_suffix_string() { if (m_use_coloring) { return ""; } else { return " :no-color"; } } /////////////////////// // AsmRet /////////////////////// IR_AsmRet::IR_AsmRet(bool use_coloring) : IR_Asm(use_coloring) {} std::string IR_AsmRet::print() { return fmt::format(".ret{}", get_color_suffix_string()); } RegAllocInstr IR_AsmRet::to_rai() { return {}; } void IR_AsmRet::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { (void)allocs; gen->add_instr(IGen::ret(), irec); } /////////////////////// // AsmFNop /////////////////////// IR_AsmFNop::IR_AsmFNop() : IR_Asm(false) {} std::string IR_AsmFNop::print() { return ".nop.vf"; } RegAllocInstr IR_AsmFNop::to_rai() { return {}; } void IR_AsmFNop::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { (void)allocs; gen->add_instr(IGen::nop_vf(), irec); } /////////////////////// // AsmFWait /////////////////////// IR_AsmFWait::IR_AsmFWait() : IR_Asm(false) {} std::string IR_AsmFWait::print() { return ".wait.vf"; } RegAllocInstr IR_AsmFWait::to_rai() { return {}; } void IR_AsmFWait::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { (void)allocs; gen->add_instr(IGen::wait_vf(), irec); } /////////////////////// // AsmPush /////////////////////// IR_AsmPush::IR_AsmPush(bool use_coloring, const RegVal* src) : IR_Asm(use_coloring), m_src(src) {} std::string IR_AsmPush::print() { return fmt::format(".push{} {}", get_color_suffix_string(), m_src->print()); } RegAllocInstr IR_AsmPush::to_rai() { RegAllocInstr rai; if (m_use_coloring) { rai.read.push_back(m_src->ireg()); } return rai; } void IR_AsmPush::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { if (m_use_coloring) { gen->add_instr(IGen::push_gpr64(get_reg(m_src, allocs, irec)), irec); } else { gen->add_instr(IGen::push_gpr64(get_no_color_reg(m_src)), irec); } } /////////////////////// // AsmPop /////////////////////// IR_AsmPop::IR_AsmPop(bool use_coloring, const RegVal* dst) : IR_Asm(use_coloring), m_dst(dst) {} std::string IR_AsmPop::print() { return fmt::format(".pop{} {}", get_color_suffix_string(), m_dst->print()); } RegAllocInstr IR_AsmPop::to_rai() { RegAllocInstr rai; if (m_use_coloring) { rai.write.push_back(m_dst->ireg()); } return rai; } void IR_AsmPop::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { if (m_use_coloring) { gen->add_instr(IGen::pop_gpr64(get_reg(m_dst, allocs, irec)), irec); } else { gen->add_instr(IGen::pop_gpr64(get_no_color_reg(m_dst)), irec); } } /////////////////////// // AsmSub /////////////////////// IR_AsmSub::IR_AsmSub(bool use_coloring, const RegVal* dst, const RegVal* src) : IR_Asm(use_coloring), m_dst(dst), m_src(src) {} std::string IR_AsmSub::print() { return fmt::format(".sub{} {}, {}", get_color_suffix_string(), m_dst->print(), m_src->print()); } RegAllocInstr IR_AsmSub::to_rai() { RegAllocInstr rai; if (m_use_coloring) { rai.write.push_back(m_dst->ireg()); rai.read.push_back(m_dst->ireg()); rai.read.push_back(m_src->ireg()); } return rai; } void IR_AsmSub::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { if (m_use_coloring) { gen->add_instr( IGen::sub_gpr64_gpr64(get_reg(m_dst, allocs, irec), get_reg(m_src, allocs, irec)), irec); } else { gen->add_instr(IGen::sub_gpr64_gpr64(get_no_color_reg(m_dst), get_no_color_reg(m_src)), irec); } } /////////////////////// // AsmAdd /////////////////////// IR_AsmAdd::IR_AsmAdd(bool use_coloring, const RegVal* dst, const RegVal* src) : IR_Asm(use_coloring), m_dst(dst), m_src(src) {} std::string IR_AsmAdd::print() { return fmt::format(".add{} {}, {}", get_color_suffix_string(), m_dst->print(), m_src->print()); } RegAllocInstr IR_AsmAdd::to_rai() { RegAllocInstr rai; if (m_use_coloring) { rai.write.push_back(m_dst->ireg()); rai.read.push_back(m_dst->ireg()); rai.read.push_back(m_src->ireg()); } return rai; } void IR_AsmAdd::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { if (m_use_coloring) { gen->add_instr( IGen::add_gpr64_gpr64(get_reg(m_dst, allocs, irec), get_reg(m_src, allocs, irec)), irec); } else { gen->add_instr(IGen::add_gpr64_gpr64(get_no_color_reg(m_dst), get_no_color_reg(m_src)), irec); } } /////////////////////// // AsmGetSymbolValue /////////////////////// IR_GetSymbolValueAsm::IR_GetSymbolValueAsm(bool use_coloring, const RegVal* dest, std::string sym_name, bool sext) : IR_Asm(use_coloring), m_dest(dest), m_sym_name(std::move(sym_name)), m_sext(sext) {} std::string IR_GetSymbolValueAsm::print() { return fmt::format(".load-sym{} {} [{}]", get_color_suffix_string(), m_dest->print(), m_sym_name); } RegAllocInstr IR_GetSymbolValueAsm::to_rai() { RegAllocInstr rai; if (m_use_coloring) { rai.write.push_back(m_dest->ireg()); } return rai; } void IR_GetSymbolValueAsm::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { auto dst_reg = m_use_coloring ? get_reg(m_dest, allocs, irec) : get_no_color_reg(m_dest); if (m_sext) { auto instr = gen->add_instr( IGen::load32s_gpr64_gpr64_plus_gpr64_plus_s32( dst_reg, gRegInfo.get_st_reg(), gRegInfo.get_offset_reg(), LINK_SYM_NO_OFFSET_FLAG), irec); gen->link_instruction_symbol_mem(instr, m_sym_name); } else { auto instr = gen->add_instr( IGen::load32u_gpr64_gpr64_plus_gpr64_plus_s32( dst_reg, gRegInfo.get_st_reg(), gRegInfo.get_offset_reg(), LINK_SYM_NO_OFFSET_FLAG), irec); gen->link_instruction_symbol_mem(instr, m_sym_name); } } /////////////////////// // AsmJumpReg /////////////////////// IR_JumpReg::IR_JumpReg(bool use_coloring, const RegVal* src) : IR_Asm(use_coloring), m_src(src) {} std::string IR_JumpReg::print() { return fmt::format(".jr{} {}", get_color_suffix_string(), m_src->print()); } RegAllocInstr IR_JumpReg::to_rai() { RegAllocInstr rai; if (m_use_coloring) { rai.read.push_back(m_src->ireg()); } return rai; } void IR_JumpReg::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { auto src_reg = m_use_coloring ? get_reg(m_src, allocs, irec) : get_no_color_reg(m_src); gen->add_instr(IGen::jmp_r64(src_reg), irec); } /////////////////////// // AsmRegSet /////////////////////// IR_RegSetAsm::IR_RegSetAsm(bool use_color, const RegVal* dst, const RegVal* src) : IR_Asm(use_color), m_dst(dst), m_src(src) {} std::string IR_RegSetAsm::print() { return fmt::format(".mov{} {} {}", get_color_suffix_string(), m_dst->print(), m_src->print()); } RegAllocInstr IR_RegSetAsm::to_rai() { RegAllocInstr rai; if (m_use_coloring) { rai.write.push_back(m_dst->ireg()); rai.read.push_back(m_src->ireg()); } return rai; } void IR_RegSetAsm::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { regset_common(gen, allocs, irec, m_dst, m_src, m_use_coloring); } /////////////////////// // AsmVF3 /////////////////////// IR_VFMath3Asm::IR_VFMath3Asm(bool use_color, const RegVal* dst, const RegVal* src1, const RegVal* src2, Kind kind) : IR_Asm(use_color), m_dst(dst), m_src1(src1), m_src2(src2), m_kind(kind) {} std::string IR_VFMath3Asm::print() { std::string function = ""; switch (m_kind) { case Kind::XOR: function = ".xor.vf"; break; case Kind::SUB: function = ".sub.vf"; break; case Kind::ADD: function = ".add.vf"; break; case Kind::MUL: function = ".mul.vf"; break; case Kind::MAX: function = ".max.vf"; break; case Kind::MIN: function = ".min.vf"; break; case Kind::DIV: function = ".div.vf"; break; default: ASSERT(false); } return fmt::format("{}{} {}, {}, {}", function, get_color_suffix_string(), m_dst->print(), m_src1->print(), m_src2->print()); } RegAllocInstr IR_VFMath3Asm::to_rai() { RegAllocInstr rai; if (m_use_coloring) { rai.write.push_back(m_dst->ireg()); rai.read.push_back(m_src1->ireg()); rai.read.push_back(m_src2->ireg()); } return rai; } void IR_VFMath3Asm::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { auto dst = get_reg_asm(m_dst, allocs, irec, m_use_coloring); auto src1 = get_reg_asm(m_src1, allocs, irec, m_use_coloring); auto src2 = get_reg_asm(m_src2, allocs, irec, m_use_coloring); switch (m_kind) { case Kind::XOR: gen->add_instr(IGen::xor_vf(dst, src1, src2), irec); break; case Kind::SUB: gen->add_instr(IGen::sub_vf(dst, src1, src2), irec); break; case Kind::ADD: gen->add_instr(IGen::add_vf(dst, src1, src2), irec); break; case Kind::MUL: gen->add_instr(IGen::mul_vf(dst, src1, src2), irec); break; case Kind::MAX: gen->add_instr(IGen::max_vf(dst, src1, src2), irec); break; case Kind::MIN: gen->add_instr(IGen::min_vf(dst, src1, src2), irec); break; case Kind::DIV: gen->add_instr(IGen::div_vf(dst, src1, src2), irec); break; default: ASSERT(false); } } /////////////////////// // IR_Int128Math3Asm /////////////////////// IR_Int128Math3Asm::IR_Int128Math3Asm(bool use_color, const RegVal* dst, const RegVal* src1, const RegVal* src2, Kind kind) : IR_Asm(use_color), m_dst(dst), m_src1(src1), m_src2(src2), m_kind(kind) {} std::string IR_Int128Math3Asm::print() { std::string function = ""; switch (m_kind) { case Kind::PEXTLB: function = ".pextlb"; break; case Kind::PEXTLH: function = ".pextlh"; break; case Kind::PEXTLW: function = ".pextlw"; break; case Kind::PEXTUB: function = ".pextub"; break; case Kind::PEXTUH: function = ".pextuh"; break; case Kind::PEXTUW: function = ".pextuw"; break; case Kind::PCPYLD: function = ".pcpyld"; break; case Kind::PCPYUD: function = ".pcpyud"; break; case Kind::PSUBW: function = ".psubw"; break; case Kind::PCEQB: function = ".pceqb"; break; case Kind::PCEQH: function = ".pceqh"; break; case Kind::PCEQW: function = ".pceqw"; break; case Kind::PCGTB: function = ".pcgtb"; break; case Kind::PCGTH: function = ".pcgth"; break; case Kind::PCGTW: function = ".pcgtw"; break; case Kind::POR: function = ".por"; break; case Kind::PXOR: function = ".pxor"; break; case Kind::PAND: function = ".pand"; break; case Kind::PACKUSWB: function = ".packuswb"; break; default: ASSERT(false); } return fmt::format("{}{} {}, {}, {}", function, get_color_suffix_string(), m_dst->print(), m_src1->print(), m_src2->print()); } RegAllocInstr IR_Int128Math3Asm::to_rai() { RegAllocInstr rai; if (m_use_coloring) { rai.write.push_back(m_dst->ireg()); rai.read.push_back(m_src1->ireg()); rai.read.push_back(m_src2->ireg()); } return rai; } void IR_Int128Math3Asm::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { auto dst = get_reg_asm(m_dst, allocs, irec, m_use_coloring); auto src1 = get_reg_asm(m_src1, allocs, irec, m_use_coloring); auto src2 = get_reg_asm(m_src2, allocs, irec, m_use_coloring); switch (m_kind) { case Kind::PEXTUB: // NOTE: this is intentionally swapped because x86 and PS2 do this opposite ways. gen->add_instr(IGen::pextub_swapped(dst, src2, src1), irec); break; case Kind::PEXTUH: // NOTE: this is intentionally swapped because x86 and PS2 do this opposite ways. gen->add_instr(IGen::pextuh_swapped(dst, src2, src1), irec); break; case Kind::PEXTUW: // NOTE: this is intentionally swapped because x86 and PS2 do this opposite ways. gen->add_instr(IGen::pextuw_swapped(dst, src2, src1), irec); break; case Kind::PEXTLB: // NOTE: this is intentionally swapped because x86 and PS2 do this opposite ways. gen->add_instr(IGen::pextlb_swapped(dst, src2, src1), irec); break; case Kind::PEXTLH: // NOTE: this is intentionally swapped because x86 and PS2 do this opposite ways. gen->add_instr(IGen::pextlh_swapped(dst, src2, src1), irec); break; case Kind::PEXTLW: // NOTE: this is intentionally swapped because x86 and PS2 do this opposite ways. gen->add_instr(IGen::pextlw_swapped(dst, src2, src1), irec); break; case Kind::PCPYLD: // NOTE: this is intentionally swapped because x86 and PS2 do this opposite ways. gen->add_instr(IGen::pcpyld_swapped(dst, src2, src1), irec); break; case Kind::PCPYUD: gen->add_instr(IGen::pcpyud(dst, src1, src2), irec); break; case Kind::PCEQB: gen->add_instr(IGen::parallel_compare_e_b(dst, src2, src1), irec); break; case Kind::PCEQH: gen->add_instr(IGen::parallel_compare_e_h(dst, src2, src1), irec); break; case Kind::PCEQW: gen->add_instr(IGen::parallel_compare_e_w(dst, src2, src1), irec); break; case Kind::PCGTB: gen->add_instr(IGen::parallel_compare_gt_b(dst, src1, src2), irec); break; case Kind::PCGTH: gen->add_instr(IGen::parallel_compare_gt_h(dst, src1, src2), irec); break; case Kind::PCGTW: gen->add_instr(IGen::parallel_compare_gt_w(dst, src1, src2), irec); break; case Kind::PSUBW: // psubW on mips is psubD on x86... gen->add_instr(IGen::vpsubd(dst, src1, src2), irec); break; case Kind::POR: gen->add_instr(IGen::parallel_bitwise_or(dst, src2, src1), irec); break; case Kind::PXOR: gen->add_instr(IGen::parallel_bitwise_xor(dst, src2, src1), irec); break; case Kind::PAND: gen->add_instr(IGen::parallel_bitwise_and(dst, src2, src1), irec); break; case Kind::PACKUSWB: gen->add_instr(IGen::vpackuswb(dst, src1, src2), irec); break; default: ASSERT(false); } } /////////////////////// // AsmVF2 /////////////////////// IR_VFMath2Asm::IR_VFMath2Asm(bool use_color, const RegVal* dst, const RegVal* src, Kind kind) : IR_Asm(use_color), m_dst(dst), m_src(src), m_kind(kind) {} std::string IR_VFMath2Asm::print() { std::string function; switch (m_kind) { case Kind::ITOF: function = ".itof.vf"; break; case Kind::FTOI: function = ".ftoi.vf"; break; default: ASSERT(false); } return fmt::format("{}{} {}, {}", function, get_color_suffix_string(), m_dst->print(), m_src->print()); } RegAllocInstr IR_VFMath2Asm::to_rai() { RegAllocInstr rai; if (m_use_coloring) { rai.write.push_back(m_dst->ireg()); rai.read.push_back(m_src->ireg()); } return rai; } void IR_VFMath2Asm::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { auto dst = get_reg_asm(m_dst, allocs, irec, m_use_coloring); auto src = get_reg_asm(m_src, allocs, irec, m_use_coloring); switch (m_kind) { case Kind::ITOF: gen->add_instr(IGen::itof_vf(dst, src), irec); break; case Kind::FTOI: gen->add_instr(IGen::ftoi_vf(dst, src), irec); break; default: ASSERT(false); } } /////////////////////// // AsmInt128-2 /////////////////////// IR_Int128Math2Asm::IR_Int128Math2Asm(bool use_color, const RegVal* dst, const RegVal* src, Kind kind, std::optional imm) : IR_Asm(use_color), m_dst(dst), m_src(src), m_kind(kind), m_imm(std::move(imm)) {} std::string IR_Int128Math2Asm::print() { std::string function; bool use_imm = false; switch (m_kind) { case Kind::PW_SLL: use_imm = true; function = ".pw.sll"; break; case Kind::PW_SRL: use_imm = true; function = ".pw.srl"; break; case Kind::PW_SRA: use_imm = true; function = ".pw.sra"; break; case Kind::PH_SLL: use_imm = true; function = ".ph.sll"; break; case Kind::PH_SRL: use_imm = true; function = ".ph.srl"; break; case Kind::VPSRLDQ: use_imm = true; function = ".VPSRLDQ"; break; case Kind::VPSLLDQ: use_imm = true; function = ".VPSLLDQ"; break; case Kind::VPSHUFLW: use_imm = true; function = ".VPSHUFLW"; break; case Kind::VPSHUFHW: use_imm = true; function = ".VPSHUFHW"; break; default: ASSERT(false); } if (use_imm) { ASSERT(m_imm.has_value()); return fmt::format("{}{} {}, {}, {}", function, get_color_suffix_string(), m_dst->print(), m_src->print(), *m_imm); } else { return fmt::format("{}{} {}, {}", function, get_color_suffix_string(), m_dst->print(), m_src->print()); } } RegAllocInstr IR_Int128Math2Asm::to_rai() { RegAllocInstr rai; if (m_use_coloring) { rai.write.push_back(m_dst->ireg()); rai.read.push_back(m_src->ireg()); } return rai; } void IR_Int128Math2Asm::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { auto dst = get_reg_asm(m_dst, allocs, irec, m_use_coloring); auto src = get_reg_asm(m_src, allocs, irec, m_use_coloring); switch (m_kind) { case Kind::PW_SLL: // you are technically allowed to put values > 32 in here. ASSERT(m_imm.has_value()); ASSERT(*m_imm >= 0); ASSERT(*m_imm <= 255); gen->add_instr(IGen::pw_sll(dst, src, *m_imm), irec); break; case Kind::PW_SRL: ASSERT(m_imm.has_value()); ASSERT(*m_imm >= 0); ASSERT(*m_imm <= 255); gen->add_instr(IGen::pw_srl(dst, src, *m_imm), irec); break; case Kind::PH_SLL: // you are technically allowed to put values > 32 in here. ASSERT(m_imm.has_value()); ASSERT(*m_imm >= 0); ASSERT(*m_imm <= 255); gen->add_instr(IGen::ph_sll(dst, src, *m_imm), irec); break; case Kind::PH_SRL: ASSERT(m_imm.has_value()); ASSERT(*m_imm >= 0); ASSERT(*m_imm <= 255); gen->add_instr(IGen::ph_srl(dst, src, *m_imm), irec); break; case Kind::PW_SRA: ASSERT(m_imm.has_value()); ASSERT(*m_imm >= 0); ASSERT(*m_imm <= 255); gen->add_instr(IGen::pw_sra(dst, src, *m_imm), irec); break; case Kind::VPSRLDQ: ASSERT(m_imm.has_value()); ASSERT(*m_imm >= 0); ASSERT(*m_imm <= 255); gen->add_instr(IGen::vpsrldq(dst, src, *m_imm), irec); break; case Kind::VPSLLDQ: ASSERT(m_imm.has_value()); ASSERT(*m_imm >= 0); ASSERT(*m_imm <= 255); gen->add_instr(IGen::vpslldq(dst, src, *m_imm), irec); break; case Kind::VPSHUFLW: ASSERT(m_imm.has_value()); ASSERT(*m_imm >= 0); ASSERT(*m_imm <= 255); gen->add_instr(IGen::vpshuflw(dst, src, *m_imm), irec); break; case Kind::VPSHUFHW: ASSERT(m_imm.has_value()); ASSERT(*m_imm >= 0); ASSERT(*m_imm <= 255); gen->add_instr(IGen::vpshufhw(dst, src, *m_imm), irec); break; default: ASSERT(false); } } // ---- Blend VF IR_BlendVF::IR_BlendVF(bool use_color, const RegVal* dst, const RegVal* src1, const RegVal* src2, u8 mask) : IR_Asm(use_color), m_dst(dst), m_src1(src1), m_src2(src2), m_mask(mask) {} std::string IR_BlendVF::print() { return fmt::format(".blend.vf{} {}, {}, {}, {}", get_color_suffix_string(), m_dst->print(), m_src1->print(), m_src2->print(), m_mask); } RegAllocInstr IR_BlendVF::to_rai() { RegAllocInstr rai; if (m_use_coloring) { rai.write.push_back(m_dst->ireg()); rai.read.push_back(m_src1->ireg()); rai.read.push_back(m_src2->ireg()); } return rai; } void IR_BlendVF::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { auto dst = get_reg_asm(m_dst, allocs, irec, m_use_coloring); auto src1 = get_reg_asm(m_src1, allocs, irec, m_use_coloring); auto src2 = get_reg_asm(m_src2, allocs, irec, m_use_coloring); gen->add_instr(IGen::blend_vf(dst, src1, src2, m_mask), irec); } // ----- Splat VF IR_SplatVF::IR_SplatVF(bool use_color, const RegVal* dst, const RegVal* src, const emitter::Register::VF_ELEMENT element) : IR_Asm(use_color), m_dst(dst), m_src(src), m_element(element) {} std::string IR_SplatVF::print() { return fmt::format(".splat.vf{} {}, {}, {}", get_color_suffix_string(), m_dst->print(), m_src->print(), m_element); } RegAllocInstr IR_SplatVF::to_rai() { RegAllocInstr rai; if (m_use_coloring) { rai.write.push_back(m_dst->ireg()); rai.read.push_back(m_src->ireg()); } return rai; } void IR_SplatVF::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { auto dst = get_reg_asm(m_dst, allocs, irec, m_use_coloring); auto src = get_reg_asm(m_src, allocs, irec, m_use_coloring); gen->add_instr(IGen::splat_vf(dst, src, m_element), irec); } // ---- Swizzle VF IR_SwizzleVF::IR_SwizzleVF(bool use_color, const RegVal* dst, const RegVal* src, const u8 controlBytes) : IR_Asm(use_color), m_dst(dst), m_src(src), m_controlBytes(controlBytes) {} std::string IR_SwizzleVF::print() { return fmt::format(".swizzle.vf{} {}, {}, {}", get_color_suffix_string(), m_dst->print(), m_src->print(), m_controlBytes); } RegAllocInstr IR_SwizzleVF::to_rai() { RegAllocInstr rai; if (m_use_coloring) { rai.write.push_back(m_dst->ireg()); rai.read.push_back(m_src->ireg()); } return rai; } void IR_SwizzleVF::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { auto dst = get_reg_asm(m_dst, allocs, irec, m_use_coloring); auto src = get_reg_asm(m_src, allocs, irec, m_use_coloring); gen->add_instr(IGen::swizzle_vf(dst, src, m_controlBytes), irec); } // ---- Square Root VF IR_SqrtVF::IR_SqrtVF(bool use_color, const RegVal* dst, const RegVal* src) : IR_Asm(use_color), m_dst(dst), m_src(src) {} std::string IR_SqrtVF::print() { return fmt::format(".sqrt.vf{} {}, {}", get_color_suffix_string(), m_dst->print(), m_src->print()); } RegAllocInstr IR_SqrtVF::to_rai() { RegAllocInstr rai; if (m_use_coloring) { rai.write.push_back(m_dst->ireg()); rai.read.push_back(m_src->ireg()); } return rai; } void IR_SqrtVF::do_codegen(emitter::ObjectGenerator* gen, const AllocationResult& allocs, emitter::IR_Record irec) { auto dst = get_reg_asm(m_dst, allocs, irec, m_use_coloring); auto src = get_reg_asm(m_src, allocs, irec, m_use_coloring); gen->add_instr(IGen::sqrt_vf(dst, src), irec); }