#include #include #include #include #include #include #include #include struct ASMLine { enum class Type { Label, Instruction, Directive }; ASMLine(Type t, std::string te) : type(t), text(std::move(te)) {} Type type; std::string text; }; struct Operand { enum class Type { empty, literal, reg /*ister*/ }; Type type = Type::empty; int reg_num = 0; std::string value; Operand() = default; Operand(const Type t, std::string v) : type(t), value(std::move(v)) { assert(type == Type::literal); } Operand(const Type t, const int num) : type(t), reg_num(num) { assert(type == Type::reg); } }; struct mos6502 : ASMLine { enum class OpCode { unknown, lda, ldy, eor, sta, sty, pha, pla, lsr, AND, cmp, bne, beq, jmp, adc, sbc, rts }; static bool get_is_branch(const OpCode o) { switch (o) { case OpCode::beq: case OpCode::bne: return true; case OpCode::lda: case OpCode::ldy: case OpCode::eor: case OpCode::sta: case OpCode::sty: case OpCode::pha: case OpCode::pla: case OpCode::lsr: case OpCode::AND: case OpCode::cmp: case OpCode::jmp: case OpCode::adc: case OpCode::sbc: case OpCode::rts: case OpCode::unknown: break; } return false; } static bool get_is_comparison(const OpCode o) { switch (o) { case OpCode::cmp: return true; case OpCode::lda: case OpCode::ldy: case OpCode::eor: case OpCode::sta: case OpCode::sty: case OpCode::pha: case OpCode::pla: case OpCode::lsr: case OpCode::AND: case OpCode::jmp: case OpCode::bne: case OpCode::beq: case OpCode::adc: case OpCode::sbc: case OpCode::rts: case OpCode::unknown: break; } return false; } explicit mos6502(const OpCode o) : ASMLine(Type::Instruction, to_string(o)), opcode(o), is_branch(get_is_branch(o)), is_comparison(get_is_comparison(o)) { } mos6502(const Type t, std::string s) : ASMLine(t, std::move(s)) { } mos6502(const OpCode o, Operand t_o) : ASMLine(Type::Instruction, to_string(o)), opcode(o), op(std::move(t_o)), is_branch(get_is_branch(o)), is_comparison(get_is_comparison(o)) { } static std::string to_string(const OpCode o) { switch (o) { case OpCode::lda: return "lda"; case OpCode::ldy: return "ldy"; case OpCode::eor: return "eor"; case OpCode::sta: return "sta"; case OpCode::sty: return "sty"; case OpCode::pha: return "pha"; case OpCode::pla: return "pla"; case OpCode::lsr: return "lsr"; case OpCode::AND: return "and"; case OpCode::cmp: return "cmp"; case OpCode::bne: return "bne"; case OpCode::beq: return "beq"; case OpCode::jmp: return "jmp"; case OpCode::adc: return "adc"; case OpCode::sbc: return "sbc"; case OpCode::rts: return "rts"; case OpCode::unknown: return ""; }; return ""; } std::string to_string() const { switch (type) { case ASMLine::Type::Label: return text + ':'; case ASMLine::Type::Directive: case ASMLine::Type::Instruction: return '\t' + text + ' ' + op.value; }; throw std::runtime_error("Unable to render: " + text); } OpCode opcode = OpCode::unknown; Operand op; bool is_branch = false; bool is_comparison = false; }; struct i386 : ASMLine { enum class OpCode { unknown, movzbl, movzwl, shrb, xorl, andl, ret, movb, cmpb, movl, jmp, jne, je, testb, addl, subl }; static OpCode parse_opcode(Type t, const std::string &o) { switch(t) { case Type::Label: return OpCode::unknown; case Type::Directive: return OpCode::unknown; case Type::Instruction: { if (o == "movzwl") return OpCode::movzwl; if (o == "movzbl") return OpCode::movzbl; if (o == "shrb") return OpCode::shrb; if (o == "xorl") return OpCode::xorl; if (o == "andl") return OpCode::andl; if (o == "ret") return OpCode::ret; if (o == "movb") return OpCode::movb; if (o == "cmpb") return OpCode::cmpb; if (o == "movl") return OpCode::movl; if (o == "jmp") return OpCode::jmp; if (o == "testb") return OpCode::testb; if (o == "jne") return OpCode::jne; if (o == "je") return OpCode::je; if (o == "subl") return OpCode::subl; if (o == "addl") return OpCode::addl; } } throw std::runtime_error("Unknown opcode: " + o); } static Operand parse_operand(std::string o) { if (o.empty()) { return Operand(); } if (o[0] == '%') { if (o == "%eax") { return Operand(Operand::Type::reg, 1); } else if (o == "%al") { return Operand(Operand::Type::reg, 1); } else if (o == "%edx") { return Operand(Operand::Type::reg, 4); } else if (o == "%dl") { return Operand(Operand::Type::reg, 4); } else if (o == "%di") { return Operand(Operand::Type::reg, 6); } else { throw std::runtime_error("Unknown register operand: '" + o + "'"); } } else { return Operand(Operand::Type::literal, o); } } i386(const int t_line_num, std::string t_line_text, Type t, std::string t_opcode, std::string o1="", std::string o2="") : ASMLine(t, t_opcode), line_num(t_line_num), line_text(std::move(t_line_text)), opcode(parse_opcode(t, t_opcode)), operand1(parse_operand(std::move(o1))), operand2(parse_operand(std::move(o2))) { } int line_num; std::string line_text; OpCode opcode; Operand operand1; Operand operand2; }; void translate_instruction(std::vector &instructions, const i386::OpCode op, const Operand &o1, const Operand &o2) { switch(op) { case i386::OpCode::ret: instructions.emplace_back(mos6502::OpCode::rts); break; case i386::OpCode::movb: if (o1.type == Operand::Type::literal && o2.type == Operand::Type::literal) { instructions.emplace_back(mos6502::OpCode::ldy, Operand(o1.type, "#" + o1.value)); instructions.emplace_back(mos6502::OpCode::sty, o2); } else if (o1.type == Operand::Type::reg && o1.reg_num == 1 && o2.type == Operand::Type::literal) { instructions.emplace_back(mos6502::OpCode::sta, o2); } else if (o1.type == Operand::Type::reg && o1.reg_num == 4 && o2.type == Operand::Type::literal) { instructions.emplace_back(mos6502::OpCode::ldy, Operand(Operand::Type::literal, "$00")); instructions.emplace_back(mos6502::OpCode::sty, o2); } else { throw std::runtime_error("Cannot translate instruction"); } break; case i386::OpCode::movl: if (o1.type == Operand::Type::reg && o1.reg_num == 1 && o2.type == Operand::Type::reg && o2.reg_num == 4) { instructions.emplace_back(mos6502::OpCode::sta, Operand(Operand::Type::literal, "$00")); } else { throw std::runtime_error("Cannot translate instruction"); } break; case i386::OpCode::movzbl: if (o1.type == Operand::Type::literal && o2.type == Operand::Type::reg && o2.reg_num == 1) { instructions.emplace_back(mos6502::OpCode::lda, o1); } else if (o1.type == Operand::Type::literal && o2.type == Operand::Type::reg && o2.reg_num == 4) { instructions.emplace_back(mos6502::OpCode::ldy, o1); instructions.emplace_back(mos6502::OpCode::sty, Operand(Operand::Type::literal, "$00")); } else { throw std::runtime_error("Cannot translate instruction"); } break; case i386::OpCode::shrb: if (o1.type == Operand::Type::reg && o1.reg_num == 1) { instructions.emplace_back(mos6502::OpCode::lsr, Operand(Operand::Type::literal, "a")); } else if (o1.type == Operand::Type::reg && o1.reg_num == 4) { instructions.emplace_back(mos6502::OpCode::lsr, Operand(Operand::Type::literal, "$00")); } else { throw std::runtime_error("Cannot translate instruction"); } break; case i386::OpCode::testb: if (o1.type == Operand::Type::literal && o2.type == Operand::Type::reg && o2.reg_num == 1) { instructions.emplace_back(mos6502::OpCode::AND, Operand(o1.type, "#" + o1.value)); instructions.emplace_back(mos6502::OpCode::cmp, Operand(Operand::Type::literal, "#$0")); } else { throw std::runtime_error("Cannot translate instruction"); } break; case i386::OpCode::jne: instructions.emplace_back(mos6502::OpCode::bne, o1); break; case i386::OpCode::je: instructions.emplace_back(mos6502::OpCode::beq, o1); break; case i386::OpCode::jmp: instructions.emplace_back(mos6502::OpCode::jmp, o1); break; case i386::OpCode::xorl: if (o1.type == Operand::Type::literal && o2.type == Operand::Type::reg && o2.reg_num == 1) { instructions.emplace_back(mos6502::OpCode::eor, Operand(o1.type, "#" + o1.value)); } else if (o1.type == Operand::Type::reg && o2.reg_num == 1 && o2.type == Operand::Type::reg && o2.reg_num == 1) { // cheater shortcut on x86 to 0 out a register instructions.emplace_back(mos6502::OpCode::lda, Operand(Operand::Type::literal, "$00")); } else if (o1.type == Operand::Type::literal && o2.type == Operand::Type::reg && o2.reg_num == 4) { instructions.emplace_back(mos6502::OpCode::pha); // transfer memory through A register, pushing and popping around it instructions.emplace_back(mos6502::OpCode::lda, Operand(Operand::Type::literal, "$00")); instructions.emplace_back(mos6502::OpCode::eor, Operand(o1.type, "#" + o1.value)); instructions.emplace_back(mos6502::OpCode::sta, Operand(Operand::Type::literal, "$00")); instructions.emplace_back(mos6502::OpCode::pla); } else { throw std::runtime_error("Cannot translate instruction"); } break; case i386::OpCode::addl: if (o1.type == Operand::Type::literal && o2.type == Operand::Type::reg && o2.reg_num == 1) { instructions.emplace_back(mos6502::OpCode::adc, Operand(o1.type, "#" + o1.value)); } else if (o1.type == Operand::Type::literal && o2.type == Operand::Type::reg && o2.reg_num == 4) { instructions.emplace_back(mos6502::OpCode::pha); // transfer memory through A register, pushing and popping around it instructions.emplace_back(mos6502::OpCode::lda, Operand(Operand::Type::literal, "$00")); instructions.emplace_back(mos6502::OpCode::adc, Operand(o1.type, "#" + o1.value)); instructions.emplace_back(mos6502::OpCode::sta, Operand(Operand::Type::literal, "$00")); instructions.emplace_back(mos6502::OpCode::pla); } else { throw std::runtime_error("Cannot translate instruction"); } break; case i386::OpCode::cmpb: if (o1.type == Operand::Type::reg && o1.reg_num == 1 && o2.type == Operand::Type::reg && o2.reg_num == 4) { instructions.emplace_back(mos6502::OpCode::cmp, Operand(Operand::Type::literal, "$00")); } else { throw std::runtime_error("Cannot translate instruction"); } break; case i386::OpCode::andl: if (o1.type == Operand::Type::literal && o2.type == Operand::Type::reg && o2.reg_num == 1) { instructions.emplace_back(mos6502::OpCode::AND, Operand(o1.type, "#" + o1.value)); } else if (o1.type == Operand::Type::literal && o2.type == Operand::Type::reg && o2.reg_num == 4) { instructions.emplace_back(mos6502::OpCode::pha); // transfer memory through A register, pushing and popping around it instructions.emplace_back(mos6502::OpCode::lda, Operand(o1.type, "#" + o1.value)); instructions.emplace_back(mos6502::OpCode::AND, Operand(Operand::Type::literal, "$00")); instructions.emplace_back(mos6502::OpCode::sta, Operand(Operand::Type::literal, "$00")); instructions.emplace_back(mos6502::OpCode::pla); // transfer memory through A register, pushing and popping around it } else { throw std::runtime_error("Cannot translate instruction"); } break; case i386::OpCode::subl: if (o1.type == Operand::Type::literal && o2.type == Operand::Type::reg && o2.reg_num == 1) { instructions.emplace_back(mos6502::OpCode::sbc, Operand(o1.type, "#" + o1.value)); } else { throw std::runtime_error("Cannot translate instruction"); } break; default: throw std::runtime_error("Cannot translate instruction"); }; } enum class LogLevel { Warning, Error }; std::string to_string(const LogLevel ll) { switch (ll) { case LogLevel::Warning: return "warning"; case LogLevel::Error: return "error"; } return "unknown"; } void log(LogLevel ll, const i386 &i, const std::string &message) { std::cerr << i.line_num << ": " << to_string(ll) << ": " << message << ": `" << i.line_text << "`\n"; } void log(LogLevel ll, const int line_no, const std::string &line, const std::string &message) { std::cerr << line_no << ": " << to_string(ll) << ": " << message << ": `" << line << "`\n"; } void to_mos6502(const i386 &i, std::vector &instructions) { try { switch(i.type) { case ASMLine::Type::Label: instructions.emplace_back(i.type, i.text); return; case ASMLine::Type::Directive: throw std::runtime_error("Directives not understood during translation"); case ASMLine::Type::Instruction: translate_instruction(instructions, i.opcode, i.operand1, i.operand2); return; } } catch (const std::exception &e) { log(LogLevel::Error, i, e.what()); } } bool optimize(std::vector &instructions) { if (instructions.size() < 2) { return false; } for (size_t op = 0; op < instructions.size() - 1; ++op) { if (instructions[op].opcode == mos6502::OpCode::pla && instructions[op+1].opcode == mos6502::OpCode::pha) { instructions.erase(std::next(std::begin(instructions), op), std::next(std::begin(instructions), op+2)); return true; } } return false; } bool fix_overwritten_flags(std::vector &instructions) { if (instructions.size() < 3) { return false; } for (size_t op = 0; op < instructions.size() - 2; ++op) { if (instructions[op].is_comparison && !instructions[op + 1].is_comparison && !instructions[op + 1].is_branch && instructions[op + 2].is_branch) { const auto opcode_to_duplicate = op + 1; const auto new_pos_1 = op + 3; const auto new_pos_2 = [&instructions, branch = instructions[op + 2].op.value](){ for (size_t cur_op = 0; cur_op < instructions.size(); ++cur_op) { if (instructions[cur_op].type == ASMLine::Type::Label && instructions[cur_op].text == branch) { return cur_op + 1; } } throw std::runtime_error("Unable to find matching branch!"); }(); instructions.insert(std::next(std::begin(instructions), std::max(new_pos_1, new_pos_2)), instructions[opcode_to_duplicate]); instructions.insert(std::next(std::begin(instructions), std::min(new_pos_1, new_pos_2)), instructions[opcode_to_duplicate]); instructions.erase(std::next(std::begin(instructions), opcode_to_duplicate)); return true; } } return false; } int main() { std::regex Label(R"(^(\S+):)"); std::regex Directive(R"(^\t(\..+))"); std::regex UnaryInstruction(R"(^\t(\S+)\s+(\S+))"); std::regex BinaryInstruction(R"(^\t(\S+)\s+(\S+),\s+(\S+))"); std::regex Instruction(R"(^\t(\S+))"); int lineno = 0; std::vector instructions; while (std::cin.good()) { std::string line; getline(std::cin, line); try { std::smatch match; if (std::regex_match(line, match, Label)) { instructions.emplace_back(lineno, line, ASMLine::Type::Label, match[1]); } else if (std::regex_match(line, match, Directive)) { instructions.emplace_back(lineno, line, ASMLine::Type::Directive, match[1]); } else if (std::regex_match(line, match, UnaryInstruction)) { instructions.emplace_back(lineno, line, ASMLine::Type::Instruction, match[1], match[2]); } else if (std::regex_match(line, match, BinaryInstruction)) { instructions.emplace_back(lineno, line, ASMLine::Type::Instruction, match[1], match[2], match[3]); } else if (std::regex_match(line, match, Instruction)) { instructions.emplace_back(lineno, line, ASMLine::Type::Instruction, match[1]); } else if (line == "") { //std::cout << "EmptyLine\n"; } else { throw std::runtime_error("Unparsed Input, Line: " + std::to_string(lineno)); } } catch (const std::exception &e) { log(LogLevel::Error, lineno, line, e.what()); } ++lineno; } std::set labels; for (const auto i : instructions) { if (i.type == ASMLine::Type::Label) { labels.insert(i.text); } } std::set used_labels{"main"}; for (const auto i : instructions) { if (i.type == ASMLine::Type::Instruction) { if (labels.count(i.operand1.value) != 0) { used_labels.insert(i.operand1.value); } if (labels.count(i.operand2.value) != 0) { used_labels.insert(i.operand2.value); } } } // remove all labels and directives that we don't need instructions.erase( std::remove_if(std::begin(instructions), std::end(instructions), [&used_labels](const auto &i){ if (i.type == ASMLine::Type::Directive) return true; if (i.type == ASMLine::Type::Label) { if (used_labels.count(i.text) == 0) { // remove all unused labels that aren't 'main' return true; } } return false; } ), std::end(instructions) ); // remove everything up to the first label // this will probably leave some dead code around at some point // but it's a start instructions.erase( std::begin(instructions), std::find_if(std::begin(instructions), std::end(instructions), [](const auto &i){ return i.type == ASMLine::Type::Label; } ) ); const auto new_labels = [&used_labels](){ std::map result; for (const auto &l : used_labels) { auto newl = l; std::transform(newl.begin(), newl.end(), newl.begin(), [](const auto c) { return std::tolower(c); }); newl.erase(std::remove_if(newl.begin(), newl.end(), [](const auto c){ return !std::isalnum(c); }), std::end(newl)); std::cout << "Old label: '" << l << "' new label: '" << newl << "'\n"; result.emplace(std::make_pair(l, newl)); } return result; }(); for (auto &i : instructions) { if (i.type == ASMLine::Type::Label) { std::cout << "Updating label " << i.text << '\n';; i.text = new_labels.at(i.text); } const auto itr1 = new_labels.find(i.operand1.value); if (itr1 != new_labels.end()) { i.operand1.value = itr1->second; } const auto itr2 = new_labels.find(i.operand2.value); if (itr2 != new_labels.end()) { i.operand1.value = itr2->second; } } std::vector new_instructions; for (const auto &i : instructions) { to_mos6502(i, new_instructions); } while (fix_overwritten_flags(new_instructions)) { // do it however many times it takes } while (optimize(new_instructions)) { // do it however many times it takes } for (const auto i : new_instructions) { std::cout << i.to_string() << '\n'; } }