Skip to content

Commit

Permalink
Merge pull request #1497 from wiremod/e2-optimizer
Browse files Browse the repository at this point in the history
Add an optimizer for E2 between the parser and compiler
  • Loading branch information
AbigailBuccaneer authored Feb 1, 2018
2 parents 63b4b10 + f60ff8e commit c9535ca
Show file tree
Hide file tree
Showing 5 changed files with 240 additions and 37 deletions.
34 changes: 3 additions & 31 deletions lua/entities/gmod_wire_expression2/base/compiler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -648,38 +648,10 @@ function Compiler:InstrIWC(args)
self:Error("Connected operator (->" .. E2Lib.limitString(op, 10) .. ") can only be used on inputs or outputs", args)
end
end

function Compiler:InstrNUM(args)
function Compiler:InstrLITERAL(args)
self.prfcounter = self.prfcounter + 0.5
RunString("E2Lib.Compiler.native = function() return " .. args[3] .. " end")
return { Compiler.native }, "n"
end

function Compiler:InstrNUMI(args)
self.prfcounter = self.prfcounter + 1
Compiler.native = { 0, tonumber(args[3]) }
RunString("local value = E2Lib.Compiler.native E2Lib.Compiler.native = function() return value end")
return { Compiler.native }, "c"
end

function Compiler:InstrNUMJ(args)
self.prfcounter = self.prfcounter + 1
Compiler.native = { 0, 0, tonumber(args[3]), 0 }
RunString("local value = E2Lib.Compiler.native E2Lib.Compiler.native = function() return value end")
return { Compiler.native }, "q"
end

function Compiler:InstrNUMK(args)
self.prfcounter = self.prfcounter + 1
Compiler.native = { 0, 0, 0, tonumber(args[3]) }
RunString("local value = E2Lib.Compiler.native E2Lib.Compiler.native = function() return value end")
return { Compiler.native }, "q"
end

function Compiler:InstrSTR(args)
self.prfcounter = self.prfcounter + 1.0
RunString(string.format("E2Lib.Compiler.native = function() return %q end", args[3]))
return { Compiler.native }, "s"
local value = args[3]
return { function() return value end }, args[4]
end

function Compiler:InstrVAR(args)
Expand Down
185 changes: 185 additions & 0 deletions lua/entities/gmod_wire_expression2/base/optimizer.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
--[[
An optimizer for E2 abstract syntax trees, as produced by the parser and
consumed by the compiler.
Currently it only performs some simple peephole optimizations and constant
propagation. Ideally, we'd do type inference as much as possible before
optimizing, which would give us more useful information throughout.
--]]

E2Lib.Optimizer = {}
local Optimizer = E2Lib.Optimizer
Optimizer.__index = Optimizer

local optimizerDebug = CreateConVar("wire_expression2_optimizer_debug", 0,
"Print an E2's abstract syntax tree after optimization"
)

function Optimizer.Execute(root)
local ok, result = xpcall(Optimizer.Process, E2Lib.errorHandler, root)
if ok and optimizerDebug:GetBool() then
print(E2Lib.Parser.DumpTree(result))
end
return ok, result
end

Optimizer.Passes = {}

function Optimizer.Process(tree)
for i = 3, #tree do
local child = tree[i]
if type(child) == "table" and child.__instruction then
tree[i] = Optimizer.Process(child)
end
end
for _, pass in ipairs(Optimizer.Passes) do
local action = pass[tree[1]]
if action then
tree = assert(action(tree))
end
end
tree.__instruction = true
return tree
end

local constantPropagation = {}

local function evaluateBinary(instruction)
-- this is a little sneaky: we use the operators previously registered with getOperator
-- to do compile-time evaluation, even though it really wasn't designed for it.
local op = wire_expression2_funcs["op:" .. instruction[1] .. "(" .. instruction[3][4] .. instruction[4][4] .. ")"]
local x, y = instruction[3][3], instruction[4][3]

local value = op[3](nil, {nil, {function() return x end}, {function() return y end}})
local type = op[2]
return {"literal", instruction[2], value, type}
end

local function evaluateUnary(instruction)
local op = wire_expression2_funcs["op:" .. instruction[1] .. "(" .. instruction[3][4] .. ")"]
local x = instruction[3][3]

local value = op[3](nil, {nil, {function() return x end}})
local type = op[2]
return {"literal", instruction[2], value, type}
end

for _, operator in pairs({ "add", "sub", "mul", "div", "mod", "exp", "eq", "neq", "geq", "leq",
"gth", "lth", "band", "band", "bor", "bxor", "bshl", "bshr" }) do
constantPropagation[operator] = function(instruction)
if instruction[3][1] ~= "literal" or instruction[4][1] ~= "literal" then return instruction end
return evaluateBinary(instruction)
end
end

function constantPropagation.neg(instruction)
if instruction[3][1] ~= "literal" then return instruction end
return evaluateUnary(instruction)
end

constantPropagation["not"] = function(instruction)
if instruction[3][1] ~= "literal" then return instruction end
instruction[3] = evaluateUnary({"is", instruction[2], instruction[3]})
return evaluateUnary(instruction)
end

for _, operator in pairs({ "and", "or" }) do
constantPropagation[operator] = function(instruction)
if instruction[3][1] ~= "literal" then return instruction end
instruction[3] = evaluateUnary({"is", instruction[2], instruction[3]})
instruction[4] = evaluateUnary({"is", instruction[2], instruction[4]})
return evaluateBinary(instruction)
end
end

table.insert(Optimizer.Passes, constantPropagation)


local peephole = {}
function peephole.add(instruction)
-- (add 0 x) → x
if instruction[3][1] == "literal" and instruction[3][3] == 0 then return instruction[4] end
-- (add x 0) → x
if instruction[4][1] == "literal" and instruction[4][3] == 0 then return instruction[3] end
-- (add (neg x) (neg y)) → (neg (add x y))
if instruction[3][1] == "neg" and instruction[4][1] == "neg" then
return {"neg", instruction[2], {"add", instruction[2], instruction[3][3], instruction[4][3],
__instruction = true}}
end
-- (add x (neg y)) → (sub x y)
if instruction[4][1] == "neg" then
return {"sub", instruction[2], instruction[3], instruction[4][3]}
end
-- (add (neg x) y) → (sub y x)
if instruction[3][1] == "neg" then
return {"sub", instruction[2], instruction[4], instruction[3][3]}
end
return instruction
end

function peephole.sub(instruction)
-- (sub 0 x) → (neg x)
if instruction[3][1] == "literal" and instruction[3][3] == 0 then
return {"neg", instruction[2], instruction[4]}
end
-- (sub x 0) → x
if instruction[4][1] == "literal" and instruction[4][3] == 0 then return instruction[3] end
-- (sub (neg x) (neg y)) → (sub y x)
if instruction[3][1] == "neg" and instruction[4][1] == "neg" then
return {"sub", instruction[2], instruction[4][3], instruction[3][3]}
end
-- (sub x (neg y) → (add x y))
if instruction[4][1] == "neg" then
return {"add", instruction[2], instruction[3], instruction[4][3]}
end
-- (sub (neg x) y) → (neg (add x y))
if instruction[3][1] == "neg" then
return {"neg", instruction[2], {"add", instruction[2], instruction[3][3], instruction[4],
__instruction = true }}
end
return instruction
end

function peephole.mul(instruction)
if instruction[4][1] == "literal" and instruction[3][1] ~= "literal" then
instruction[3], instruction[4] = instruction[4], instruction[3]
end
-- (mul 1 x) → x
if instruction[3][1] == "literal" and instruction[3][3] == 1 then return instruction[4] end
-- (mul 0 x) → 0
if instruction[3][1] == "literal" and instruction[3][3] == 0 then return instruction[3] end
-- (mul -1 x) → (neg x)
if instruction[3][1] == "literal" and instruction[3][3] == -1 then
return {"neg", instruction[2], instruction[4]}
end
return instruction
end

function peephole.neg(instruction)
-- (neg (neg x)) → x
if instruction[3][1] == "neg" then return instruction[3][3] end
return instruction
end

peephole["if"] = function(instruction)
-- (if 1 x y) → x
-- (if 0 x y) → y
if instruction[3][1] == "literal" then
instruction[3] = evaluateUnary({"is", instruction[2], instruction[3]})
if instruction[3][3] == 1 then return instruction[4] end
if instruction[3][3] == 0 then return instruction[5] end
assert(false, "unreachable: `is` evaluation didn't return a boolean")
end
return instruction
end

function peephole.whl(instruction)
-- (while 0 x) → (seq)
if instruction[3][1] == "literal" then
instruction[3] = evaluateUnary({"is", instruction[2], instruction[3]})
if instruction[3][3] == 0 then return {"seq", instruction[2]} end
end
return instruction
end

table.insert(Optimizer.Passes, peephole)
48 changes: 42 additions & 6 deletions lua/entities/gmod_wire_expression2/base/parser.lua
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ E2Lib.Parser = {}
local Parser = E2Lib.Parser
Parser.__index = Parser

local parserDebug = CreateConVar("wire_expression2_parser_debug", 0,
"Print an E2's abstract syntax tree after parsing"
)

function Parser.Execute(...)
-- instantiate Parser
local instance = setmetatable({}, Parser)
Expand All @@ -90,6 +94,23 @@ function Parser.Execute(...)
return xpcall(Parser.Process, E2Lib.errorHandler, instance, ...)
end

function Parser.DumpTree(tree, indentation)
indentation = indentation or ''
local str = indentation .. tree[1] .. '(' .. tree[2][1] .. ':' .. tree[2][2] .. ')\n'
indentation = indentation .. ' '
for i = 3, #tree do
local child = tree[i]
if type(child) == 'table' and child.__instruction then
str = str .. Parser.DumpTree(child, indentation)
elseif type(child) == 'string' then
str = str .. indentation .. string.format('%q', child) .. '\n'
else
str = str .. indentation .. tostring(child) .. '\n'
end
end
return str
end

function Parser:Error(message, token)
if token then
error(message .. " at line " .. token[4] .. ", char " .. token[5], 0)
Expand All @@ -107,7 +128,9 @@ function Parser:Process(tokens, params)

self:NextToken()
local tree = self:Root()

if parserDebug:GetBool() then
print(Parser.DumpTree(tree))
end
return tree, self.delta, self.includes
end

Expand All @@ -127,7 +150,7 @@ end


function Parser:Instruction(trace, name, ...)
return { name, trace, ... }
return { __instruction = true, name, trace, ... }
end


Expand Down Expand Up @@ -1337,16 +1360,29 @@ function Parser:Expr17()
local trace = self:GetTokenTrace()
local tokendata = self:GetTokenData()
if isnumber(tokendata) then
return self:Instruction(trace, "num", tokendata)
return self:Instruction(trace, "literal", tokendata, "n")
end
local num, suffix = tokendata:match("^([-+e0-9.]*)(.*)$")
num = assert(tonumber(num), "unparseable numeric literal")
local value, type
if suffix == "" then
value, type = num, "n"
elseif suffix == "i" then
value, type = {0, num}, "c"
elseif suffix == "j" then
value, type = {0, 0, num, 0}, "q"
elseif suffix == "k" then
value, type = {0, 0, 0, num}, "q"
else
assert(false, "unrecognized numeric suffix " .. suffix)
end
local num, tp = tokendata:match("^([-+e0-9.]*)(.*)$")
return self:Instruction(trace, "num" .. tp, num)
return self:Instruction(trace, "literal", value, type)
end

if self:AcceptRoamingToken("str") then
local trace = self:GetTokenTrace()
local str = self:GetTokenData()
return self:Instruction(trace, "str", str)
return self:Instruction(trace, "literal", str, "s")
end

if self:AcceptRoamingToken("trg") then
Expand Down
9 changes: 9 additions & 0 deletions lua/entities/gmod_wire_expression2/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,9 @@ function ENT:CompileCode(buffer, files, filepath)

if not self:PrepareIncludes(files) then return end

status,tree = E2Lib.Optimizer.Execute(tree)
if not status then self:Error(tree) return end

local status, script, inst = E2Lib.Compiler.Execute(tree, self.inports[3], self.outports[3], self.persists[3], dvars, self.includes)
if not status then self:Error(script) return end

Expand Down Expand Up @@ -303,6 +306,12 @@ function ENT:PrepareIncludes(files)
return
end

status, tree = E2Lib.Optimizer.Execute(tree)
if not status then
self:Error("(" .. file .. ")" .. tree)
return
end

self.includes[file] = { tree }
end

Expand Down
1 change: 1 addition & 0 deletions lua/entities/gmod_wire_expression2/shared.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ include("core/e2lib.lua")
include("base/preprocessor.lua")
include("base/tokenizer.lua")
include("base/parser.lua")
include("base/optimizer.lua")
include("base/compiler.lua")
include('core/init.lua')

0 comments on commit c9535ca

Please sign in to comment.