488 lines
17 KiB
Lua
488 lines
17 KiB
Lua
local M = require "asm"
|
|
M.gameboy = false
|
|
|
|
local function die(msg)
|
|
error(msg, 3)
|
|
end
|
|
|
|
local function eval(v)
|
|
if type(v) == "function" then v = v() end
|
|
if type(v) == "string" then
|
|
local s = M.symbols[v]
|
|
if s ~= nil then v = s end
|
|
end
|
|
return v
|
|
end
|
|
|
|
local function byte(v)
|
|
v = eval(v)
|
|
if type(v) == "string" or type(v) == "table" then v = M.op_resolve(v) end
|
|
return M.byte_normalize(v)
|
|
end
|
|
|
|
local function signed_byte(v, sign)
|
|
v = eval(v)
|
|
if type(v) == "string" or type(v) == "table" then v = M.op_resolve(v) end
|
|
if sign == "-" then v = -v end
|
|
if v < -128 or v > 127 then die("signed byte out of range: " .. v) end
|
|
return v & 0xff
|
|
end
|
|
|
|
local function word(v)
|
|
v = eval(v)
|
|
if type(v) == "string" or type(v) == "table" then v = M.op_resolve(v) end
|
|
return M.word_normalize(v)
|
|
end
|
|
|
|
local function wordle(v)
|
|
v = word(v)
|
|
return v & 0xff, v >> 8
|
|
end
|
|
|
|
local function emit(size, bin, cycles)
|
|
table.insert(M.section_current.instructions, {
|
|
size = function() return size end,
|
|
cycles = cycles,
|
|
bin = type(bin) == "function" and bin or function() return bin end,
|
|
})
|
|
end
|
|
|
|
local function emit_rel(opc, target, cycles)
|
|
local parent, offset = M.label_current
|
|
local section, rorg = M.section_current, M.location_current.rorg
|
|
local ins = { cycles = cycles or 12 }
|
|
ins.size = function()
|
|
offset = section.size
|
|
return 2
|
|
end
|
|
ins.bin = function()
|
|
local x, label = eval(target), target
|
|
if type(label) == "string" and label:sub(1, 1) == "_" then
|
|
x = parent .. label
|
|
end
|
|
if type(x) == "string" or type(x) == "table" then x = M.op_resolve(x) end
|
|
x = x - 2 - offset - rorg(section.org)
|
|
if x < -128 or x > 127 then die("relative branch out of range: " .. x) end
|
|
return { opc, x & 0xff }
|
|
end
|
|
table.insert(M.section_current.instructions, ins)
|
|
end
|
|
|
|
function M.imm(v) return { kind = "imm", value = v } end
|
|
function M.mem(v) return { kind = "mem", value = v } end
|
|
function M.spoff(sign, v) return { kind = "spoff", sign = sign, value = v } end
|
|
function M.idx(r, sign, disp)
|
|
local d = byte(disp or 0)
|
|
if sign == "-" then d = (-d) & 0xff end
|
|
return { kind = "idx", reg = r, disp = d }
|
|
end
|
|
|
|
local r8 = { b = 0, c = 1, d = 2, e = 3, h = 4, l = 5, a = 7 }
|
|
local rp = { bc = 0, de = 1, hl = 2, sp = 3 }
|
|
local rp2 = { bc = 0, de = 1, hl = 2, af = 3 }
|
|
local cc = { nz = 0, z = 1, nc = 2, c = 3, po = 4, pe = 5, p = 6, m = 7 }
|
|
local alu = { add = 0x80, adc = 0x88, sub = 0x90, sbc = 0x98, ["and"] = 0xa0, xor = 0xa8, ["or"] = 0xb0, cp = 0xb8 }
|
|
|
|
local function is_mem(o) return type(o) == "table" and o.kind == "mem" end
|
|
local function is_idx(o) return type(o) == "table" and o.kind == "idx" end
|
|
local function is_imm(o) return type(o) == "table" and o.kind == "imm" end
|
|
local function is_spoff(o) return type(o) == "table" and o.kind == "spoff" end
|
|
local function imm_value(o)
|
|
if not is_imm(o) then die("immediate operand requires #") end
|
|
return o.value
|
|
end
|
|
local function condition_code(c)
|
|
local code = cc[c]
|
|
if code == nil then return end
|
|
if M.gameboy and code > 3 then die("condition is not available on Game Boy") end
|
|
return code
|
|
end
|
|
local function prefix_for(r)
|
|
if M.gameboy and (r == "ix" or r == "iy") then die("IX/IY are not available on Game Boy") end
|
|
return r == "ix" and 0xdd or r == "iy" and 0xfd
|
|
end
|
|
|
|
local function r8code(o)
|
|
if type(o) == "string" and r8[o] then return r8[o] end
|
|
if is_mem(o) and o.value == "hl" then return 6 end
|
|
if is_idx(o) then return 6, prefix_for(o.reg), o.disp end
|
|
end
|
|
|
|
local function emit_r8(prefix, opc, disp)
|
|
if prefix then emit(3, { prefix, opc, disp }) else emit(1, { opc }) end
|
|
end
|
|
|
|
local function emit_r8_n(prefix, opc, disp, n)
|
|
if prefix then emit(4, function() return { prefix, opc, disp, byte(n) } end) else emit(2, function() return { opc, byte(n) } end) end
|
|
end
|
|
|
|
local function rpcode(o, allow_index)
|
|
if type(o) ~= "string" then return end
|
|
if allow_index and (o == "ix" or o == "iy") then return 2, prefix_for(o) end
|
|
return rp[o]
|
|
end
|
|
|
|
local function rp2code(o, allow_index)
|
|
if type(o) ~= "string" then return end
|
|
if allow_index and (o == "ix" or o == "iy") then return 2, prefix_for(o) end
|
|
return rp2[o]
|
|
end
|
|
|
|
function M.ld(a, b)
|
|
a, b = eval(a), eval(b)
|
|
if is_spoff(b) then
|
|
if not M.gameboy or a ~= "hl" then die("unsupported ld") end
|
|
return emit(2, function() return { 0xf8, signed_byte(b.value, b.sign) } end)
|
|
end
|
|
if a == "sp" and (b == "hl" or b == "ix" or b == "iy") then
|
|
local p = prefix_for(b)
|
|
return emit(p and 2 or 1, p and { p, 0xf9 } or { 0xf9 })
|
|
end
|
|
local ac, ap, ad = r8code(a)
|
|
local bc, bp, bd = r8code(b)
|
|
if ac and bc then
|
|
if ap and bp then die("cannot use two indexed operands") end
|
|
local p, d = ap or bp, ad or bd
|
|
return emit_r8(p, 0x40 | (ac << 3) | bc, d)
|
|
end
|
|
if ac and not is_mem(b) then return emit_r8_n(ap, 0x06 | (ac << 3), ad, imm_value(b)) end
|
|
if a == "a" and is_mem(b) and (b.value == "bc" or b.value == "de") then
|
|
return emit(1, { b.value == "bc" and 0x0a or 0x1a })
|
|
end
|
|
if is_mem(a) and (a.value == "bc" or a.value == "de") and b == "a" then
|
|
return emit(1, { a.value == "bc" and 0x02 or 0x12 })
|
|
end
|
|
if M.gameboy and a == "a" and is_mem(b) and b.value == "c" then
|
|
return emit(1, { 0xf2 })
|
|
end
|
|
if M.gameboy and is_mem(a) and a.value == "c" and b == "a" then
|
|
return emit(1, { 0xe2 })
|
|
end
|
|
if a == "a" and is_mem(b) then
|
|
return emit(3, function() local lo, hi = wordle(b.value); return { M.gameboy and 0xfa or 0x3a, lo, hi } end)
|
|
end
|
|
if is_mem(a) and b == "a" then
|
|
return emit(3, function() local lo, hi = wordle(a.value); return { M.gameboy and 0xea or 0x32, lo, hi } end)
|
|
end
|
|
if (a == "i" or a == "r") and b == "a" then return emit(2, { 0xed, a == "i" and 0x47 or 0x4f }) end
|
|
if a == "a" and (b == "i" or b == "r") then return emit(2, { 0xed, b == "i" and 0x57 or 0x5f }) end
|
|
|
|
local rc, p = rpcode(a, true)
|
|
if rc and not is_mem(b) then
|
|
return emit(p and 4 or 3, function()
|
|
local lo, hi = wordle(imm_value(b))
|
|
return p and { p, 0x01 | (rc << 4), lo, hi } or { 0x01 | (rc << 4), lo, hi }
|
|
end)
|
|
end
|
|
if is_mem(a) then
|
|
rc, p = rpcode(b, true)
|
|
if M.gameboy and b == "sp" then
|
|
return emit(3, function() local lo, hi = wordle(a.value); return { 0x08, lo, hi } end)
|
|
end
|
|
if M.gameboy then die("unsupported ld") end
|
|
if rc then
|
|
return emit((p or b ~= "hl") and 4 or 3, function()
|
|
local lo, hi = wordle(a.value)
|
|
if p then return { p, 0x22, lo, hi } end
|
|
if b == "hl" then return { 0x22, lo, hi } end
|
|
return { 0xed, 0x43 | (rc << 4), lo, hi }
|
|
end)
|
|
end
|
|
end
|
|
if is_mem(b) then
|
|
if M.gameboy then die("unsupported ld") end
|
|
rc, p = rpcode(a, true)
|
|
if rc then
|
|
return emit((p or a ~= "hl") and 4 or 3, function()
|
|
local lo, hi = wordle(b.value)
|
|
if p then return { p, 0x2a, lo, hi } end
|
|
if a == "hl" then return { 0x2a, lo, hi } end
|
|
return { 0xed, 0x4b | (rc << 4), lo, hi }
|
|
end)
|
|
end
|
|
end
|
|
die("unsupported ld")
|
|
end
|
|
|
|
-- Operator assignments use the shortest Game Boy encoding available for
|
|
-- absolute loads through A. Mnemonic ld/ldh remain explicit.
|
|
function M.ld_auto(a, b)
|
|
a, b = eval(a), eval(b)
|
|
if not M.gameboy then return M.ld(a, b) end
|
|
|
|
local address, absolute_opcode, high_opcode
|
|
if a == "a" and is_mem(b) and type(b.value) ~= "string" then
|
|
address, absolute_opcode, high_opcode = b.value, 0xfa, 0xf0
|
|
elseif is_mem(a) and type(a.value) ~= "string" and b == "a" then
|
|
address, absolute_opcode, high_opcode = a.value, 0xea, 0xe0
|
|
else
|
|
return M.ld(a, b)
|
|
end
|
|
|
|
local ins = {}
|
|
ins.size = function()
|
|
local ok, value = M.pcall_za(word, address)
|
|
if not ok then return 3 end
|
|
if value >= 0xff00 then
|
|
ins.size = 2
|
|
ins.bin = function() return { high_opcode, value & 0xff } end
|
|
return 2
|
|
end
|
|
ins.size = 3
|
|
ins.bin = function() return { absolute_opcode, value & 0xff, value >> 8 } end
|
|
return 3
|
|
end
|
|
ins.bin = function()
|
|
local value = word(address)
|
|
return { absolute_opcode, value & 0xff, value >> 8 }
|
|
end
|
|
table.insert(M.section_current.instructions, ins)
|
|
end
|
|
|
|
local function alu_a(name, a, b)
|
|
a, b = eval(a), eval(b)
|
|
if b ~= nil then
|
|
if a ~= "a" then die(name .. " only supports A as explicit accumulator") end
|
|
a = b
|
|
end
|
|
local rc, p, d = r8code(a)
|
|
if rc then return emit_r8(p, alu[name] | rc, d) end
|
|
emit(2, function() return { alu[name] | 0x46, byte(imm_value(a)) } end)
|
|
end
|
|
|
|
function M.add(a, b)
|
|
if M.gameboy and a == "sp" and b ~= nil then
|
|
return emit(2, function() return { 0xe8, byte(imm_value(eval(b))) } end)
|
|
end
|
|
if b ~= nil and (a == "hl" or a == "ix" or a == "iy") then
|
|
local rc = rpcode(b, a == "ix" or a == "iy")
|
|
if rc == nil then die("unsupported add") end
|
|
local p = prefix_for(a)
|
|
return emit(p and 2 or 1, p and { p, 0x09 | (rc << 4) } or { 0x09 | (rc << 4) })
|
|
end
|
|
return alu_a("add", a, b)
|
|
end
|
|
|
|
for _, name in ipairs{ "adc", "sbc" } do
|
|
M[name] = function(a, b)
|
|
if b ~= nil and a == "hl" then
|
|
if M.gameboy then die(name .. " hl is not available on Game Boy") end
|
|
local rc = rpcode(b)
|
|
if rc == nil then die("unsupported " .. name) end
|
|
return emit(2, { 0xed, (name == "adc" and 0x4a or 0x42) | (rc << 4) })
|
|
end
|
|
return alu_a(name, a, b)
|
|
end
|
|
end
|
|
|
|
for _, name in ipairs{ "sub", "and", "or", "xor", "cp" } do
|
|
M[name] = function(a) return alu_a(name, a) end
|
|
end
|
|
M.ana = M["and"]
|
|
M.ora = M["or"]
|
|
|
|
local function incdec(name, o)
|
|
o = eval(o)
|
|
local rc, p, d = r8code(o)
|
|
if rc then return emit_r8(p, (name == "inc" and 0x04 or 0x05) | (rc << 3), d) end
|
|
local r, rpfx = rpcode(o, true)
|
|
if r then return emit(rpfx and 2 or 1, rpfx and { rpfx, (name == "inc" and 0x03 or 0x0b) | (r << 4) } or { (name == "inc" and 0x03 or 0x0b) | (r << 4) }) end
|
|
die("unsupported " .. name)
|
|
end
|
|
function M.inc(o) return incdec("inc", o) end
|
|
function M.dec(o) return incdec("dec", o) end
|
|
|
|
function M.jp(a, b)
|
|
a, b = eval(a), eval(b)
|
|
if b then
|
|
local c = condition_code(a)
|
|
if not c then die("invalid jp condition") end
|
|
return emit(3, function() local lo, hi = wordle(b); return { 0xc2 | (c << 3), lo, hi } end)
|
|
end
|
|
if is_mem(a) and (a.value == "hl" or a.value == "ix" or a.value == "iy") then
|
|
local p = prefix_for(a.value)
|
|
return emit(p and 2 or 1, p and { p, 0xe9 } or { 0xe9 })
|
|
end
|
|
emit(3, function() local lo, hi = wordle(a); return { 0xc3, lo, hi } end)
|
|
end
|
|
|
|
function M.jr(a, b)
|
|
a, b = eval(a), eval(b)
|
|
if b then
|
|
local c = ({ nz = 0x20, z = 0x28, nc = 0x30, c = 0x38 })[a]
|
|
if not c then die("invalid jr condition") end
|
|
return emit_rel(c, b, 12)
|
|
end
|
|
emit_rel(0x18, a, 12)
|
|
end
|
|
function M.djnz(a) emit_rel(0x10, a, 13) end
|
|
|
|
function M.call(a, b)
|
|
a, b = eval(a), eval(b)
|
|
if b then
|
|
local c = condition_code(a)
|
|
if not c then die("invalid call condition") end
|
|
return emit(3, function() local lo, hi = wordle(b); return { 0xc4 | (c << 3), lo, hi } end)
|
|
end
|
|
emit(3, function() local lo, hi = wordle(a); return { 0xcd, lo, hi } end)
|
|
end
|
|
|
|
function M.ret(c)
|
|
c = eval(c)
|
|
if c then
|
|
local code = condition_code(c)
|
|
if not code then die("invalid ret condition") end
|
|
return emit(1, { 0xc0 | (code << 3) })
|
|
end
|
|
emit(1, { 0xc9 })
|
|
end
|
|
function M.reti() emit(2, { 0xed, 0x4d }) end
|
|
function M.retn()
|
|
if M.gameboy then die("retn is not available on Game Boy") end
|
|
emit(2, { 0xed, 0x45 })
|
|
end
|
|
function M.rst(n)
|
|
emit(1, function()
|
|
n = byte(imm_value(eval(n)))
|
|
if n & 0xc7 ~= 0 or n > 0x38 then die("invalid rst vector") end
|
|
return { 0xc7 | n }
|
|
end)
|
|
end
|
|
|
|
function M.push(r)
|
|
r = eval(r)
|
|
local c, p = rp2code(r, true)
|
|
if c == nil then die("unsupported push") end
|
|
emit(p and 2 or 1, p and { p, 0xc5 | (c << 4) } or { 0xc5 | (c << 4) })
|
|
end
|
|
function M.pop(r)
|
|
r = eval(r)
|
|
local c, p = rp2code(r, true)
|
|
if c == nil then die("unsupported pop") end
|
|
emit(p and 2 or 1, p and { p, 0xc1 | (c << 4) } or { 0xc1 | (c << 4) })
|
|
end
|
|
|
|
local rotbase = { rlc = 0x00, rrc = 0x08, rl = 0x10, rr = 0x18, sla = 0x20, sra = 0x28, srl = 0x38 }
|
|
for name, base in pairs(rotbase) do
|
|
M[name] = function(o)
|
|
o = eval(o)
|
|
local rc, p, d = r8code(o)
|
|
if not rc then die("unsupported " .. name) end
|
|
return emit(p and 4 or 2, p and { p, 0xcb, d, base | rc } or { 0xcb, base | rc })
|
|
end
|
|
end
|
|
for name, base in pairs{ bit = 0x40, res = 0x80, set = 0xc0 } do
|
|
M[name] = function(bit, o)
|
|
bit, o = eval(bit), eval(o)
|
|
local rc, p, d = r8code(o)
|
|
if not rc then die("unsupported " .. name) end
|
|
local opc = base | (byte(imm_value(bit)) << 3) | rc
|
|
return emit(p and 4 or 2, p and { p, 0xcb, d, opc } or { 0xcb, opc })
|
|
end
|
|
end
|
|
|
|
function M.swap(o)
|
|
o = eval(o)
|
|
local rc, p, d = r8code(o)
|
|
if not rc then die("unsupported swap") end
|
|
if not M.gameboy then die("swap is only available in Game Boy mode") end
|
|
return emit(p and 4 or 2, p and { p, 0xcb, d, 0x30 | rc } or { 0xcb, 0x30 | rc })
|
|
end
|
|
|
|
function M.ex(a, b)
|
|
if M.gameboy then die("ex is not available on Game Boy") end
|
|
a, b = eval(a), eval(b)
|
|
if a == "de" and b == "hl" then return emit(1, { 0xeb }) end
|
|
if a == "af" and b == "af2" then return emit(1, { 0x08 }) end
|
|
if is_mem(a) and a.value == "sp" and (b == "hl" or b == "ix" or b == "iy") then
|
|
local p = prefix_for(b)
|
|
return emit(p and 2 or 1, p and { p, 0xe3 } or { 0xe3 })
|
|
end
|
|
die("unsupported ex")
|
|
end
|
|
|
|
function M.im(n)
|
|
if M.gameboy then die("im is not available on Game Boy") end
|
|
emit(2, function() return { 0xed, ({ [0] = 0x46, 0x56, 0x5e })[byte(imm_value(eval(n)))] or die("invalid interrupt mode") } end)
|
|
end
|
|
|
|
M["in"] = function(a, b)
|
|
if M.gameboy then die("in is not available on Game Boy") end
|
|
a, b = eval(a), eval(b)
|
|
if a == "a" and is_mem(b) and b.value ~= "c" then return emit(2, function() return { 0xdb, byte(b.value) } end) end
|
|
local rc = r8[a]
|
|
if rc and is_mem(b) and b.value == "c" then return emit(2, { 0xed, 0x40 | (rc << 3) }) end
|
|
die("unsupported in")
|
|
end
|
|
function M.out(a, b)
|
|
if M.gameboy then die("out is not available on Game Boy") end
|
|
a, b = eval(a), eval(b)
|
|
if is_mem(a) and a.value ~= "c" and b == "a" then return emit(2, function() return { 0xd3, byte(a.value) } end) end
|
|
local rc = r8[b]
|
|
if is_mem(a) and a.value == "c" and rc then return emit(2, { 0xed, 0x41 | (rc << 3) }) end
|
|
die("unsupported out")
|
|
end
|
|
|
|
for name, opc in pairs{
|
|
ccf=0x3f, cpl=0x2f, daa=0x27, di=0xf3, ei=0xfb, exx=0xd9, halt=0x76, nop=0x00,
|
|
rla=0x17, rlca=0x07, rra=0x1f, rrca=0x0f, scf=0x37,
|
|
} do M[name] = function()
|
|
if M.gameboy and name == "exx" then die("exx is not available on Game Boy") end
|
|
emit(1, { opc })
|
|
end end
|
|
|
|
function M.stop()
|
|
if not M.gameboy then die("stop is only available in Game Boy mode") end
|
|
emit(2, { 0x10, 0x00 })
|
|
end
|
|
|
|
for name, opc in pairs{
|
|
neg=0x44, rld=0x6f, rrd=0x67, ldi=0xa0, ldir=0xb0, ldd=0xa8, lddr=0xb8,
|
|
cpi=0xa1, cpir=0xb1, cpd=0xa9, cpdr=0xb9, ini=0xa2, inir=0xb2, ind=0xaa, indr=0xba,
|
|
outi=0xa3, otir=0xb3, outd=0xab, otdr=0xbb,
|
|
} do M[name] = function()
|
|
if M.gameboy then die(name .. " is not available on Game Boy") end
|
|
emit(2, { 0xed, opc })
|
|
end end
|
|
|
|
do
|
|
local z80_reti = M.reti
|
|
M.reti = function()
|
|
if M.gameboy then return emit(1, { 0xd9 }) end
|
|
return z80_reti()
|
|
end
|
|
end
|
|
|
|
function M.ldi(a, b)
|
|
a, b = eval(a), eval(b)
|
|
if not M.gameboy then return emit(2, { 0xed, 0xa0 }) end
|
|
if is_mem(a) and a.value == "hl" and b == "a" then return emit(1, { 0x22 }) end
|
|
if a == "a" and is_mem(b) and b.value == "hl" then return emit(1, { 0x2a }) end
|
|
die("unsupported ldi")
|
|
end
|
|
|
|
function M.ldd(a, b)
|
|
a, b = eval(a), eval(b)
|
|
if not M.gameboy then return emit(2, { 0xed, 0xa8 }) end
|
|
if is_mem(a) and a.value == "hl" and b == "a" then return emit(1, { 0x32 }) end
|
|
if a == "a" and is_mem(b) and b.value == "hl" then return emit(1, { 0x3a }) end
|
|
die("unsupported ldd")
|
|
end
|
|
|
|
function M.ldh(a, b)
|
|
a, b = eval(a), eval(b)
|
|
if not M.gameboy then die("ldh is only available in Game Boy mode") end
|
|
local high_byte = function(v)
|
|
v = word(v)
|
|
if v >= 0xff00 and v <= 0xffff then return v & 0xff end
|
|
return M.byte_normalize(v)
|
|
end
|
|
if is_mem(a) and a.value == "c" and b == "a" then return emit(1, { 0xe2 }) end
|
|
if a == "a" and is_mem(b) and b.value == "c" then return emit(1, { 0xf2 }) end
|
|
if is_mem(a) and b == "a" then return emit(2, function() return { 0xe0, high_byte(a.value) } end) end
|
|
if a == "a" and is_mem(b) then return emit(2, function() return { 0xf0, high_byte(b.value) } end) end
|
|
die("unsupported ldh")
|
|
end
|
|
|
|
return M
|