350 lines
8.7 KiB
Lua
350 lines
8.7 KiB
Lua
local CG = {}
|
|
CG.__index = CG
|
|
|
|
local AST = require"ast"
|
|
local Target = require"target"
|
|
local ETypes = require"etype"
|
|
|
|
local SIMPLE_BINOPS = {["+"] = "add", ["-"] = "sub", ["^"] = "xor", ["|"] = "or", ["&"] = "and"}
|
|
local SIMPLE_UNOPS = {["~"] = "not", ["-"] = "neg"}
|
|
|
|
local COMP_SIGNED = {["=="] = "e", ["!="] = "ne", ["<"] = "l", [">"] = "g", [">="] = "ge", ["<="] = "le"}
|
|
local COMP_UNSIGNED = {["=="] = "e", ["!="] = "ne", ["<"] = "b", [">"] = "a", [">="] = "be", ["<="] = "ae"}
|
|
|
|
function CG:xop(ex)
|
|
if ex.kind == "expr-var" then
|
|
return ex.vreg.cgi.register
|
|
elseif ex.kind == "expr-int" then
|
|
return tostring(ex.value)
|
|
elseif ex.kind == "expr-unop" and ex.op == "*" then
|
|
-- Memory ops
|
|
if ex.a.kind == "expr-binop" and ex.a.op == "+" and ex.a.a.kind == "expr-var" and ex.a.b.kind == "expr-int" then
|
|
-- *(var1 + int)
|
|
return "[" .. ex.a.a.vreg.cgi.register .. " + " .. ex.a.b.value .. "]"
|
|
elseif ex.a.kind == "expr-binop" and ex.a.op == "+" and ex.a.a.kind == "expr-var" and ex.a.b.kind == "expr-var" then
|
|
-- *(var1 + var2)
|
|
return "[" .. ex.a.a.vreg.cgi.register .. " + " .. ex.a.b.vreg.cgi.register .. "]"
|
|
end
|
|
end
|
|
|
|
error("Unimplemented " .. tostring(ex))
|
|
end
|
|
|
|
function CG:emit(chunk)
|
|
if chunk.cgi.stack_reservation > 0 then
|
|
print("sub esp, " .. chunk.cgi.stack_reservation)
|
|
end
|
|
|
|
for _, stmt in ipairs(chunk.children) do
|
|
|
|
if stmt.kind == "stmt-assign" then
|
|
if stmt.src.kind == "expr-binop" then
|
|
assert(SIMPLE_BINOPS[stmt.src.op])
|
|
assert(self:xop(stmt.dest) == self:xop(stmt.src.a))
|
|
|
|
print(SIMPLE_BINOPS[stmt.src.op] .. " " .. self:xop(stmt.dest) .. ", " .. self:xop(stmt.src.b))
|
|
else
|
|
print("mov " .. self:xop(stmt.dest) .. ", " .. self:xop(stmt.src))
|
|
end
|
|
elseif stmt.kind == "stmt-jump" then
|
|
assert(stmt.condition.kind == "expr-binop")
|
|
|
|
local cond = stmt.condition
|
|
local op = cond.op
|
|
|
|
assert(cond.a.etype == cond.b.etype)
|
|
assert(cond.a.etype.kind == "scalar")
|
|
assert(op == ">" or op == "<" or op == "==" or op == "!=" or op == ">=" or op == "<=")
|
|
|
|
print("cmp " .. self:xop(cond.a) .. ", " .. self:xop(cond.b))
|
|
|
|
local unsigned = cond.a.etype.unsigned
|
|
|
|
print("j" .. (unsigned and COMP_UNSIGNED or COMP_SIGNED)[op] .. " .L" .. stmt.target)
|
|
elseif stmt.kind == "stmt-label" then
|
|
print(".L" .. stmt.name .. ":")
|
|
elseif stmt.kind == "stmt-return" then
|
|
if chunk.cgi.stack_reservation > 0 then
|
|
print("add esp, " .. chunk.cgi.stack_reservation)
|
|
end
|
|
|
|
print("ret")
|
|
else
|
|
error("Unimplemented " .. tostring(stmt))
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
function CG:reg_alloc(chunk, vreguses)
|
|
local vreg_live_start = {}
|
|
|
|
for stmt_idx, stmt in ipairs(chunk.children) do
|
|
if stmt.kind == "stmt-assign" and stmt.dest.kind == "expr-var" then
|
|
local vreg = stmt.dest.vreg
|
|
|
|
if vreg_live_start[vreg] then
|
|
vreg_live_start[vreg] = math.min(vreg_live_start[vreg], stmt_idx)
|
|
else
|
|
vreg_live_start[vreg] = stmt_idx
|
|
end
|
|
end
|
|
end
|
|
|
|
local vreg_live_fin = {}
|
|
|
|
for vreg, uses in pairs(vreguses) do
|
|
vreg_live_fin[vreg] = require"set".max(uses)
|
|
end
|
|
|
|
-- Determine register classes as defined in target.lua
|
|
for vreg in pairs(vreg_live_start) do
|
|
vreg.cgi = {}
|
|
if vreg.etype:byte_size() == 1 then
|
|
vreg.cgi.register_class = "reg8"
|
|
else
|
|
vreg.cgi.register_class = "regn8"
|
|
end
|
|
end
|
|
|
|
local edges = {}
|
|
for vreg1, start1 in pairs(vreg_live_start) do
|
|
edges[vreg1] = {}
|
|
|
|
for vreg2, start2 in pairs(vreg_live_start) do
|
|
if vreg1 ~= vreg2 then
|
|
local fin1 = vreg_live_fin[vreg1]
|
|
local fin2 = vreg_live_fin[vreg2]
|
|
|
|
local live_range_intersection = ((start1 <= start2 and start2 <= fin1) or (start1 <= fin2 and fin2 <= fin1))
|
|
local resource_intersection = (Target.REG_CLASSES[vreg1.cgi.register_class].mask & Target.REG_CLASSES[vreg2.cgi.register_class].mask) ~= 0
|
|
|
|
if live_range_intersection and resource_intersection then
|
|
edges[vreg1][vreg2] = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
for vreg in pairs(vreg_live_start) do
|
|
local found_reg
|
|
for _, reg in ipairs(Target.REG_CLASSES[vreg.cgi.register_class].items) do
|
|
local available = true
|
|
for vreg2 in pairs(vreg_live_start) do
|
|
if vreg2.cgi.register == reg then
|
|
available = false
|
|
break
|
|
end
|
|
end
|
|
if available then
|
|
found_reg = reg
|
|
break
|
|
end
|
|
end
|
|
|
|
if not found_reg then
|
|
error("Spilling!")
|
|
end
|
|
|
|
vreg.cgi.register = found_reg
|
|
end
|
|
end
|
|
|
|
function CG:compute_uses(chunk)
|
|
local stmt_idx = 0
|
|
|
|
local ret = {}
|
|
|
|
chunk:generic_visitor(function(n)
|
|
if n.kind:sub(1, 5) == "stmt-" then
|
|
stmt_idx = stmt_idx + 1
|
|
elseif n.kind == "expr-var" then
|
|
if not ret[n.vreg] then
|
|
ret[n.vreg] = {}
|
|
end
|
|
|
|
ret[n.vreg][stmt_idx] = true
|
|
end
|
|
end)
|
|
|
|
return ret
|
|
end
|
|
|
|
function CG:compute_defs(chunk)
|
|
local totaldefs = {}
|
|
for stmt_idx, stmt in ipairs(chunk.children) do
|
|
if stmt.kind == "stmt-assign" and stmt.dest.kind == "expr-var" then
|
|
if not totaldefs[stmt.dest.vreg] then
|
|
totaldefs[stmt.dest.vreg] = {}
|
|
end
|
|
table.insert(totaldefs[stmt.dest.vreg], stmt_idx)
|
|
end
|
|
end
|
|
|
|
local outdefs = {}
|
|
local indefs = {}
|
|
|
|
local changed = {}
|
|
for stmt_idx, stmt in ipairs(chunk.children) do
|
|
outdefs[stmt_idx] = {}
|
|
changed[stmt_idx] = true
|
|
end
|
|
|
|
while #changed > 0 do
|
|
local stmt_idx
|
|
for s in pairs(changed) do stmt_idx = s break end
|
|
changed[stmt_idx] = nil
|
|
local stmt = chunk.children[stmt_idx]
|
|
|
|
local predecessors = {}
|
|
if stmt_idx > 1 then
|
|
predecessors[stmt_idx - 1] = true
|
|
end
|
|
if stmt.kind == "stmt-label" then
|
|
for stmt2_idx, stmt2 in pairs(chunk.children) do
|
|
if stmt2.kind == "stmt-jump" and stmt2.target == stmt.name then
|
|
predecessors[stmt2_idx] = true
|
|
end
|
|
end
|
|
end
|
|
|
|
indefs[stmt_idx] = {}
|
|
for pred in pairs(predecessors) do
|
|
require"set".merge(indefs[stmt_idx], outdefs[pred])
|
|
end
|
|
|
|
local newout = {}
|
|
local kill = nil
|
|
if stmt.kind == "stmt-assign" and stmt.dest.kind == "expr-var" then
|
|
kill = stmt.dest.vreg
|
|
newout[stmt_idx] = true
|
|
end
|
|
for indef in pairs(indefs[stmt_idx]) do
|
|
if chunk.children[indef].kind ~= "stmt-assign" or chunk.children[indef].dest.kind ~= "expr-var" or chunk.children[indef].dest.vreg ~= kill then
|
|
newout[indef] = true
|
|
end
|
|
end
|
|
|
|
if require"set".equal(outdefs[stmt_idx], newout) then
|
|
break
|
|
end
|
|
|
|
local successors = {}
|
|
if stmt_idx < #chunk.children then
|
|
successors[stmt_idx + 1] = true
|
|
end
|
|
if stmt.kind == "jump" then
|
|
for stmt2_idx, stmt2 in pairs(chunk.children) do
|
|
if stmt2.kind == "label" and stmt2.name == stmt.target then
|
|
successors[stmt2_idx] = true
|
|
end
|
|
end
|
|
end
|
|
|
|
for succ in pairs(successors) do
|
|
changed[succ] = true
|
|
end
|
|
|
|
outdefs[stmt_idx] = newout
|
|
end
|
|
|
|
return indefs, outdefs
|
|
end
|
|
|
|
function CG:get_stack_vreg(chunk)
|
|
for vreg in pairs(self:compute_uses(chunk)) do
|
|
if vreg.cgi.register == "esp" then
|
|
return vreg
|
|
end
|
|
end
|
|
|
|
local vreg = AST.VReg("@stack", ETypes.scalar(true, Target.GPR_SIZE))
|
|
vreg.cgi = {}
|
|
vreg.cgi.register = "esp"
|
|
return vreg
|
|
end
|
|
|
|
function CG:pass_save(chunk)
|
|
local saves = chunk.etype.modifiers["save"]
|
|
|
|
if not saves then
|
|
return
|
|
end
|
|
|
|
local used_saves = {}
|
|
for vreg in pairs(self:compute_uses(chunk)) do
|
|
if saves[vreg.cgi.register] then
|
|
table.insert(used_saves, vreg)
|
|
end
|
|
end
|
|
|
|
if #used_saves == 0 then
|
|
return
|
|
end
|
|
|
|
local stack_vreg = self:get_stack_vreg(chunk)
|
|
|
|
for i = 1, #used_saves do
|
|
table.insert(chunk.children, i,
|
|
AST.assign(
|
|
AST.unop(stack_vreg.etype.to, "*",
|
|
AST.binop(stack_vreg.etype, AST.var(nil, stack_vreg), "+", AST.int(stack_vreg.etype, (i - 1) * Target.GPR_SIZE // 8))),
|
|
AST.var(nil, used_saves[i])))
|
|
|
|
chunk.cgi.stack_reservation = chunk.cgi.stack_reservation + 4
|
|
end
|
|
|
|
local handled_rets = {}
|
|
for stmt_idx, stmt in ipairs(chunk.children) do
|
|
if stmt.kind == "stmt-return" then
|
|
if not handled_rets[stmt] then
|
|
for i = 1, #used_saves do
|
|
table.insert(chunk.children, stmt_idx,
|
|
AST.assign(
|
|
AST.var(nil, used_saves[i]),
|
|
AST.unop(stack_vreg.etype.to, "*",
|
|
AST.binop(stack_vreg.etype, AST.var(nil, stack_vreg), "+", AST.int(stack_vreg.etype, (i - 1) * Target.GPR_SIZE // 8)))))
|
|
end
|
|
handled_rets[stmt] = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function CG:process(chunk)
|
|
assert(not chunk.cgi)
|
|
chunk.cgi = {}
|
|
chunk.cgi.stack_reservation = 0
|
|
|
|
--local indefs, outdefs = self:compute_defs(chunk)
|
|
local uses = self:compute_uses(chunk)
|
|
self:reg_alloc(chunk, uses)
|
|
|
|
self:pass_save(chunk)
|
|
end
|
|
|
|
local function apply(modules)
|
|
local cg = setmetatable({}, CG)
|
|
|
|
for module, root in pairs(modules) do
|
|
-- Go through all declarations in module
|
|
for _, decl in pairs(root.children) do
|
|
if decl.export then
|
|
print("global " .. decl.name)
|
|
end
|
|
print(decl.name .. ":")
|
|
|
|
local ex = decl.expr
|
|
|
|
if ex.etype.kind == "func" then
|
|
cg:process(ex)
|
|
cg:emit(ex)
|
|
else
|
|
error("Unimplemented " .. decl.expr.etype)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return {apply = apply}
|