mirror of
https://github.com/notwa/lips
synced 2024-04-30 00:53:23 -07:00
Connor Olding
f851540b24
reimplement modulo by 0x80000000 for labels. allow numeric label values ("REL") to be "fixed" to bypass label calculation. add offsets after label calculation instead of before. properly check for EOL after all expected arguments have been exhausted. finally, add token properties to debug dumps.
270 lines
7.5 KiB
Lua
270 lines
7.5 KiB
Lua
local insert = table.insert
|
|
|
|
local path = string.gsub(..., "[^.]+$", "")
|
|
local data = require(path.."data")
|
|
|
|
local overrides = {}
|
|
-- note: "self" is an instance of Preproc
|
|
|
|
local function tob_override(self, name)
|
|
-- handle all the addressing modes for lw/sw-like instructions
|
|
local dest = self:pop('CPU')
|
|
local offset, base
|
|
if self:peek('DEREF') then
|
|
offset = 0
|
|
base = self:pop('DEREF')
|
|
else -- NUM or LABELSYM
|
|
local o = self:pop('CONST')
|
|
if self:peek('NUM') then
|
|
local temp, err = self:pop('CONST'):compute()
|
|
if err then
|
|
self:error(err, temp)
|
|
end
|
|
o:set('offset', temp)
|
|
end
|
|
offset = self:token(o)
|
|
if not o.portion then
|
|
offset:set('portion', 'lower')
|
|
end
|
|
-- attempt to use the fewest possible instructions for this offset
|
|
if not o.portion and (o.tt == 'LABELSYM' or o.tok >= 0x80000000) then
|
|
local temp = self:token(o):set('portion', 'upperoff')
|
|
self:push_new('LUI', 'AT', temp)
|
|
if self.s[self.i] ~= nil then
|
|
local reg = self:pop('DEREF'):set('tt', 'REG')
|
|
if reg.tok ~= 'R0' then
|
|
self:push_new('ADDU', 'AT', 'AT', reg)
|
|
end
|
|
end
|
|
base = self:token('DEREF', 'AT')
|
|
else
|
|
base = self:pop('DEREF')
|
|
end
|
|
end
|
|
self:push_new(name, dest, offset, base)
|
|
end
|
|
|
|
for k, v in pairs(data.instructions) do
|
|
if v[2] == 'tob' then
|
|
overrides[k] = tob_override
|
|
end
|
|
end
|
|
|
|
function overrides:LI(name)
|
|
local dest = self:pop('CPU')
|
|
local im = self:pop('CONST')
|
|
|
|
-- for us, this is just semantics. for a "real" assembler,
|
|
-- LA could add appropriate RELO LUI/ADDIU directives.
|
|
if im.tt == 'LABELSYM' then
|
|
self:error('use LA for labels')
|
|
end
|
|
|
|
if im.portion then
|
|
-- FIXME: use appropriate instruction based on portion?
|
|
self:push_new('ADDIU', dest, 'R0', im)
|
|
return
|
|
end
|
|
|
|
im.tok = im.tok % 0x100000000
|
|
if im.tok >= 0x10000 and im.tok <= 0xFFFF8000 then
|
|
local temp = self:token(im):set('portion', 'upper')
|
|
self:push_new('LUI', dest, temp)
|
|
if im.tok % 0x10000 ~= 0 then
|
|
local temp = self:token(im):set('portion', 'lower')
|
|
self:push_new('ORI', dest, dest, temp)
|
|
end
|
|
elseif im.tok >= 0x8000 and im.tok < 0x10000 then
|
|
local temp = self:token(im):set('portion', 'lower')
|
|
self:push_new('ORI', dest, 'R0', temp)
|
|
else
|
|
local temp = self:token(im):set('portion', 'lower')
|
|
self:push_new('ADDIU', dest, 'R0', temp)
|
|
end
|
|
end
|
|
|
|
function overrides:LA(name)
|
|
local dest = self:pop('CPU')
|
|
local im = self:pop('CONST')
|
|
|
|
local im = self:token(im):set('portion', 'upperoff')
|
|
self:push_new('LUI', dest, im)
|
|
local im = self:token(im):set('portion', 'lower')
|
|
self:push_new('ADDIU', dest, dest, im)
|
|
end
|
|
|
|
function overrides:PUSH(name)
|
|
local w = name == 'PUSH' and 'SW' or 'LW'
|
|
local stack = {}
|
|
for _, t in ipairs(self.s) do
|
|
if t.tt == 'NUM' then
|
|
if t.tok < 0 then
|
|
self:error("can't push a negative number of spaces", t.tok)
|
|
end
|
|
for i=1, t.tok do
|
|
insert(stack, '')
|
|
end
|
|
self:pop()
|
|
else
|
|
insert(stack, self:pop('CPU'))
|
|
end
|
|
end
|
|
if #stack == 0 then
|
|
self:error(name..' requires at least one argument')
|
|
end
|
|
if name == 'PUSH' then
|
|
local im = self:token(#stack*4):set('negate')
|
|
self:push_new('ADDIU', 'SP', 'SP', im)
|
|
end
|
|
for i, r in ipairs(stack) do
|
|
if r ~= '' then
|
|
local offset = (i - 1)*4
|
|
self:push_new(w, r, offset, self:token('DEREF', 'SP'))
|
|
end
|
|
end
|
|
if name == 'JPOP' then
|
|
self:push_new('JR', 'RA')
|
|
end
|
|
if name == 'POP' or name == 'JPOP' then
|
|
local im = #stack * 4
|
|
self:push_new('ADDIU', 'SP', 'SP', im)
|
|
end
|
|
end
|
|
overrides.POP = overrides.PUSH
|
|
overrides.JPOP = overrides.PUSH
|
|
|
|
function overrides:NAND(name)
|
|
local dest = self:pop('CPU')
|
|
local src = self:pop('CPU')
|
|
local target = self:pop('CPU')
|
|
self:push_new('AND', dest, src, target)
|
|
self:push_new('NOR', dest, dest, 'R0') -- NOT
|
|
end
|
|
|
|
function overrides:NANDI(name)
|
|
local dest = self:pop('CPU')
|
|
local src = self:pop('CPU')
|
|
local im = self:pop('CONST')
|
|
self:push_new('ANDI', dest, src, im)
|
|
self:push_new('NOR', dest, dest, 'R0') -- NOT
|
|
end
|
|
|
|
function overrides:NORI(name)
|
|
local dest = self:pop('CPU')
|
|
local src = self:pop('CPU')
|
|
local im = self:pop('CONST')
|
|
self:push_new('ORI', dest, src, im)
|
|
self:push_new('NOR', dest, dest, 'R0') -- NOT
|
|
end
|
|
|
|
function overrides:ROL(name)
|
|
local first = name == 'ROL' and 'SLL' or 'SRL'
|
|
local second = name == 'ROL' and 'SRL' or 'SLL'
|
|
local dest = self:pop('CPU')
|
|
local src = self:pop('CPU')
|
|
local im = self:pop('CONST')
|
|
if dest == 'AT' or src == 'AT' then
|
|
self:error('registers cannot be AT in this pseudo-instruction')
|
|
end
|
|
|
|
self:push_new(first, dest, src, im)
|
|
local temp, err = im:compute()
|
|
if err then
|
|
self:error(err, temp)
|
|
end
|
|
self:push_new(second, 'AT', src, 32 - temp)
|
|
self:push_new('OR', dest, dest, 'AT')
|
|
end
|
|
overrides.ROR = overrides.ROL
|
|
|
|
function overrides:JR(name)
|
|
local src = self:peek() and self:pop('CPU') or 'RA'
|
|
self:push_new('JR', src)
|
|
end
|
|
|
|
local branch_basics = {
|
|
BEQI = 'BEQ',
|
|
BGEI = 'BEQ',
|
|
BGTI = 'BEQ',
|
|
BLEI = 'BNE',
|
|
BLTI = 'BNE',
|
|
BNEI = 'BNE',
|
|
BEQIL = 'BEQL',
|
|
BGEIL = 'BEQL',
|
|
BGTIL = 'BEQL',
|
|
BLEIL = 'BNEL',
|
|
BLTIL = 'BNEL',
|
|
BNEIL = 'BNEL',
|
|
}
|
|
|
|
function overrides:BEQI(name)
|
|
local branch = branch_basics[name]
|
|
local reg = self:pop('CPU')
|
|
local im = self:pop('CONST')
|
|
local offset = self:pop('CONST')
|
|
|
|
if reg == 'AT' then
|
|
self:error('register cannot be AT in this pseudo-instruction')
|
|
end
|
|
|
|
self:push_new('ADDIU', 'AT', 'R0', im)
|
|
|
|
self:push_new(branch, reg, 'AT', offset)
|
|
end
|
|
overrides.BNEI = overrides.BEQI
|
|
overrides.BEQIL = overrides.BEQI
|
|
overrides.BNEIL = overrides.BEQI
|
|
|
|
function overrides:BLTI(name)
|
|
local branch = branch_basics[name]
|
|
local reg = self:pop('CPU')
|
|
local im = self:pop('CONST')
|
|
local offset = self:pop('CONST')
|
|
|
|
if reg == 'AT' then
|
|
self:error('register cannot be AT in this pseudo-instruction')
|
|
end
|
|
|
|
self:push_new('SLTI', 'AT', reg, im)
|
|
|
|
self:push_new(branch, 'R0', 'AT', offset)
|
|
end
|
|
overrides.BGEI = overrides.BLTI
|
|
overrides.BLTIL = overrides.BLTI
|
|
overrides.BGEIL = overrides.BLTI
|
|
|
|
function overrides:BLEI(name)
|
|
-- TODO: this can probably be optimized
|
|
if name:sub(#name) == 'L' then
|
|
self:error('unimplemented pseudo-instruction', name)
|
|
end
|
|
local branch = branch_basics[name]
|
|
local reg = self:pop('CPU')
|
|
local im = self:pop('CONST')
|
|
local offset = self:pop('CONST')
|
|
|
|
if reg == 'AT' then
|
|
self:error('register cannot be AT in this pseudo-instruction')
|
|
end
|
|
|
|
self:push_new('ADDIU', 'AT', 'R0', im)
|
|
|
|
local beq_offset
|
|
if name == 'BLEI' or name =='BLEIL' then
|
|
beq_offset = offset
|
|
else
|
|
-- branch to delay slot of the next branch
|
|
beq_offset = self:token('NUM', 2):set('fixed')
|
|
end
|
|
self:push_new('BEQ', reg, 'AT', beq_offset)
|
|
|
|
self:push_new('SLT', 'AT', reg, 'AT')
|
|
|
|
self:push_new(branch, 'AT', 'R0', offset)
|
|
end
|
|
overrides.BGTI = overrides.BLEI
|
|
overrides.BLEIL = overrides.BLEI
|
|
overrides.BGTIL = overrides.BLEI
|
|
|
|
return overrides
|