local floor = math.floor local format = string.format local insert = table.insert local unpack = unpack or table.unpack local path = string.gsub(..., "[^.]+$", "") local data = require(path.."data") local util = require(path.."util") local overrides = require(path.."overrides") local Base = require(path.."Base") local Token = require(path.."Token") local Statement = require(path.."Statement") local bitrange = util.bitrange local Dumper = Base:extend() function Dumper:init(writer, options) self.writer = writer self.options = options or {} self.labels = setmetatable({}, {__index=options.labels}) self.commands = {} self.pos = options.offset or 0 self.lastcommand = nil end function Dumper:error(msg, got) if got ~= nil then msg = msg..', got '..tostring(got) end error(('%s:%d: Error: %s'):format(self.fn, self.line, msg), 2) end function Dumper:export_labels(t) for k, v in pairs(self.labels) do -- only return valid labels; those that don't begin with a number -- (relative labels are invalid) if not tostring(k):sub(1, 1):find('%d') then t[k] = v end end return t end function Dumper:advance(by) self.pos = self.pos + by end --[[ function Dumper:add_directive(fn, line, name, a, b) self.fn = fn self.line = line local t = {} t.fn = self.fn t.line = self.line if name == 'BYTE' then elseif name == 'HALFWORD' then elseif name == 'WORD' then if type(a) == 'string' then t.kind = 'label' t.name = a insert(self.commands, t) self:advance(4) end elseif name == 'ORG' then t.kind = 'goto' t.addr = a insert(self.commands, t) self.pos = a self:advance(0) elseif name == 'ALIGN' then t.kind = 'ahead' local align if a == 0 then align = 4 elseif a < 0 then self:error('negative alignment') else align = 2^a end local temp = self.pos + align - 1 t.skip = temp - (temp % align) - self.pos t.fill = b and b % 0x100 or 0 insert(self.commands, t) self:advance(t.skip) elseif name == 'SKIP' then t.kind = 'ahead' t.skip = a t.fill = b and b % 0x100 or nil insert(self.commands, t) self:advance(t.skip) else self:error('unimplemented directive') end end --]] function Dumper:desym(t) if t.tt == 'REL' then local target = t.tok % 0x80000000 local pos = self.pos % 0x80000000 local rel = floor(target/4) - 1 - floor(pos/4) if rel > 0x8000 or rel <= -0x8000 then self:error('branch too far') end return rel % 0x10000 elseif type(t.tok) == 'number' then if t.offset then return t.tok + t.offset end return t.tok elseif t.tt == 'REG' then assert(data.all_registers[t.tok], 'Internal Error: unknown register') return data.registers[t.tok] or data.fpu_registers[t.tok] or data.sys_registers[t.tok] elseif t.tt == 'LABELSYM' or t.tt == 'LABELREL' then local label = self.labels[t.tok] if label == nil then self:error('undefined label') end if t.offset then label = label + t.offset end if t.tt == 'LABELSYM' then return label end label = label % 0x80000000 local pos = self.pos % 0x80000000 local rel = floor(label/4) - 1 - floor(pos/4) if rel > 0x8000 or rel <= -0x8000 then self:error('branch too far') end return rel % 0x10000 end error('Internal Error: failed to desym') end function Dumper:toval(t) assert(type(t) == 'table', 'Internal Error: invalid value') local val = self:desym(t) if t.index then val = val % 0x80000000 val = floor(val/4) end if t.negate then val = -val end if t.negate or t.signed then if val >= 0x10000 or val < -0x8000 then self:error('value out of range') end val = val % 0x10000 end if t.portion == 'upper' then val = bitrange(val, 16, 31) elseif t.portion == 'lower' then val = bitrange(val, 0, 15) elseif t.portion == 'upperoff' then local upper = bitrange(val, 16, 31) local lower = bitrange(val, 0, 15) if lower >= 0x8000 then -- accommodate for offsets being signed upper = (upper + 1) % 0x10000 end val = upper end return val end function Dumper:validate(n, bits) local max = 2^bits if n == nil then self:error('value is nil') -- internal error? end if n > max or n < 0 then self:error('value out of range', n) end return n end function Dumper:valvar(t, bits) local val = self:toval(t) return self:validate(val, bits) end function Dumper:write(t) for _, b in ipairs(t) do self.writer(self.pos, b) self.pos = self.pos + 1 end end function Dumper:dump_instruction(t) local uw = 0 local lw = 0 local o = t[1] uw = uw + o*0x400 if #t == 2 then local val = self:valvar(t[2], 26) uw = uw + bitrange(val, 16, 25) lw = lw + bitrange(val, 0, 15) elseif #t == 4 then uw = uw + self:valvar(t[2], 5)*0x20 uw = uw + self:valvar(t[3], 5) lw = lw + self:valvar(t[4], 16) elseif #t == 6 then uw = uw + self:valvar(t[2], 5)*0x20 uw = uw + self:valvar(t[3], 5) lw = lw + self:valvar(t[4], 5)*0x800 lw = lw + self:valvar(t[5], 5)*0x40 lw = lw + self:valvar(t[6], 6) else error('Internal Error: unknown n-size') end return uw, lw end function Dumper:expect(tts) local t = self.s[self.i] if t == nil then self:error("expected another argument") -- TODO: more verbose end self.fn = t.fn self.line = t.line for _, tt in pairs(tts) do if t.tt == tt then return t.ok end end --local err = ("argument %i of %s expected type %s"):format(self.i, self.s.type, tt) local err = ("unexpected type for argument %i of %s"):format(self.i, self.s.type) self:error(err, t.tt) end function Dumper:register(registers) self:expect{'REG'} local t = self.s[self.i] local numeric = registers[t.tok] if not numeric then self:error('wrong type of register') end --self:advance() return numeric end function Dumper:assemble_j(first, out) local w = 0 w = w + self:validate(first, 6) * 0x04000000 w = w + self:validate(out[1], 26) * 0x00000001 local t = Token(self.fn, self.line, 'WORDS', {w}) local s = Statement(self.fn, self.line, '!DATA', t) return s end function Dumper:assemble_i(first, out) local w = 0 w = w + self:validate(first, 6) * 0x04000000 w = w + self:validate(out[1], 5) * 0x00200000 w = w + self:validate(out[2], 5) * 0x00010000 w = w + self:validate(out[3], 16) * 0x00000001 local t = Token(self.fn, self.line, 'WORDS', {w}) local s = Statement(self.fn, self.line, '!DATA', t) return s end function Dumper:assemble_r(first, out) local w = 0 w = w + self:validate(first, 6) * 0x04000000 w = w + self:validate(out[1], 5) * 0x00200000 w = w + self:validate(out[2], 5) * 0x00010000 w = w + self:validate(out[3], 5) * 0x00000800 w = w + self:validate(out[4], 5) * 0x00000040 w = w + self:validate(out[5], 6) * 0x00000001 local t = Token(self.fn, self.line, 'WORDS', {w}) local s = Statement(self.fn, self.line, '!DATA', t) return s end -- NOTE: we could move format_{in,out} to its own virtual class and inherit it function Dumper:format_in(informat) -- see data.lua for a guide on what all these mean local args = {} --if #informat ~= #s then error('mismatch') end for i=1, #informat do self.i = i -- FIXME: do we need this? local c = informat:sub(i, i) if c == 'd' and not args.rd then args.rd = self:register(data.registers) elseif c == 's' and not args.rs then args.rs = self:register(data.registers) elseif c == 't' and not args.rt then args.rt = self:register(data.registers) elseif c == 'D' and not args.fd then args.fd = self:register(data.fpu_registers) elseif c == 'S' and not args.fs then args.fs = self:register(data.fpu_registers) elseif c == 'T' and not args.ft then args.ft = self:register(data.fpu_registers) elseif c == 'X' and not args.rd then args.rd = self:register(data.sys_registers) elseif c == 'Y' and not args.rs then args.rs = self:register(data.sys_registers) elseif c == 'Z' and not args.rt then args.rt = self:register(data.sys_registers) elseif c == 'o' and not args.offset then args.offset = 0 --Token(self:const()):set('signed') elseif c == 'r' and not args.offset then args.offset = 0 --Token(self:const('relative')):set('signed') elseif c == 'i' and not args.immediate then args.immediate = 0 --self:const(nil, 'no label') elseif c == 'I' and not args.index then args.index = 0 --Token(self:const()):set('index') elseif c == 'k' and not args.immediate then args.immediate = 0 --Token(self:const(nil, 'no label')):set('negate') elseif c == 'K' and not args.immediate then args.immediate = 0 --Token(self:const(nil, 'no label')):set('signed') elseif c == 'b' and not args.base then args.base = 0 --self:deref() else error('Internal Error: invalid input formatting string') end end return args end function Dumper:format_out_raw(outformat, first, args, const, formatconst) -- see data.lua for a guide on what all these mean local lookup = { [1]=self.assemble_j, [3]=self.assemble_i, [5]=self.assemble_r, } local out = {} for i=1,#outformat do local c = outformat:sub(i, i) if c == 'd' then out[#out+1] = args.rd elseif c == 's' then out[#out+1] = args.rs elseif c == 't' then out[#out+1] = args.rt elseif c == 'D' then out[#out+1] = args.fd elseif c == 'S' then out[#out+1] = args.fs elseif c == 'T' then out[#out+1] = args.ft elseif c == 'o' then out[#out+1] = args.offset elseif c == 'i' then out[#out+1] = args.immediate elseif c == 'I' then out[#out+1] = args.index elseif c == 'b' then out[#out+1] = args.base elseif c == '0' then out[#out+1] = 0 elseif c == 'C' then out[#out+1] = const elseif c == 'F' then out[#out+1] = formatconst end end local f = lookup[#outformat] assert(f, 'Internal Error: invalid output formatting string') return f(self, first, out) end function Dumper:format_out(t, args) return self:format_out_raw(t[3], t[1], args, t[4], t[5]) end function Dumper:assemble(s) local name = s.type local h = data.instructions[name] self.s = s if overrides[name] then --overrides[name](self, name) local s = Statement(self.fn, self.line, '!DATA') -- FIXME: dummy return s elseif h[2] ~= nil then local args = self:format_in(h[2]) return self:format_out(h, args) else self:error('unimplemented instruction', name) end end local assembled_directives = { ['!DATA'] = true, ['!ORG'] = true, } function Dumper:load(statements) self.labels = {} local new_statements = {} for i=1, #statements do local s = statements[i] self.fn = s.fn self.line = s.line if s.type:sub(1, 1) == '!' then if s.type == '!LABEL' then self.labels[s[1].tok] = i elseif s.type == '!DATA' then -- noop else -- TODO: internal error? self:error('unknown statement', s.type) end end end -- TODO: keep track of self.pos and lengths here for i=1, #statements do local s = statements[i] self.fn = s.fn self.line = s.line if s.type:sub(1, 1) ~= '!' then insert(new_statements, self:assemble(s)) elseif assembled_directives[s.type] then -- FIXME: check for LABELs in !DATA -- TODO: reimplement ALIGN and SKIP here insert(new_statements, s) end end self.statements = new_statements return self.statements end function Dumper:dump() -- TODO: have options insert .org and/or .base; pos is always 0 at start self.pos = self.options.offset or 0 for i, s in ipairs(self.statements) do assert(assembled_directives[s.type], 'Internal Error: unassembled statement') if s.type == '!DATA' then for j, t in ipairs(s) do if t.tt == 'WORDS' then for _, w in ipairs(t.tok) do local b0 = bitrange(w, 0, 7) local b1 = bitrange(w, 8, 15) local b2 = bitrange(w, 16, 23) local b3 = bitrange(w, 24, 31) self:write{b3, b2, b1, b0} end elseif t.tt == 'HALFWORDS' then for _, h in ipairs(t.tok) do local b0 = bitrange(h, 0, 7) local b1 = bitrange(h, 8, 15) self:write{b1, b0} end elseif t.tt == 'BYTES' then for _, b in ipairs(t.tok) do local b0 = bitrange(h, 0, 7) self:write{b0} end else error('Internal Error: unknown !DATA token') end end elseif s.type == '!ORG' then self.pos = s[1] end end --[[ elseif t.kind == 'goto' then self.pos = t.addr elseif t.kind == 'ahead' then if t.fill then for i=1, t.skip do self:write{t.fill} end else self.pos = self.pos + t.skip end elseif t.kind == 'label' then local val = self:desym{tt='LABELSYM', tok=t.name} val = (val % 0x80000000) + 0x80000000 local b0 = bitrange(val, 0, 7) local b1 = bitrange(val, 8, 15) local b2 = bitrange(val, 16, 23) local b3 = bitrange(val, 24, 31) self:write{b3, b2, b1, b0} --]] end return Dumper