Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic-blocks-based intermediate language and flow analysis. #607

Merged
merged 21 commits into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
e1102f7
Making basic blocks i.r. from the tree i.r.
gabrielsferre Jun 20, 2024
97179c3
Adding option for printing basic blocks i.r.
gabrielsferre Jun 20, 2024
5c80ed6
Refactoring uninitialized.lua to use basic blocks
gabrielsferre Jun 20, 2024
66661ca
Generating C code using the basic blocks i.r.
gabrielsferre Jun 27, 2024
8c741d1
Not using ir.Cmd.Return on the basic blocks i.r. anymore
gabrielsferre Jun 27, 2024
730930d
Using kill sets at uninitialize.lua flow analysis
gabrielsferre Jun 27, 2024
58a70c3
Using depth-search topological order on block traversal
gabrielsferre Jun 27, 2024
9de27da
Changing gc.lua traversal of cmds so it uses blocks.
gabrielsferre Jun 25, 2024
666cf5a
Now the option --print-ir prints the basic blocks i.r.
gabrielsferre Jun 27, 2024
d6eb77f
Generating basic blocks i.r. from a.s.t.
gabrielsferre Jun 27, 2024
67050a6
Removing unused code from ir.lua
gabrielsferre Jun 27, 2024
dd66808
adding "repeat until" non-breaking loop test
gabrielsferre Jun 27, 2024
32f11e8
Fixing garbage collector's command traversal order
gabrielsferre Jun 28, 2024
21c7c71
Adding tests that currently break uninitialized.lua
gabrielsferre Jul 1, 2024
d7932dc
Replacing JmpIfFalse condition with ir.Cmd.CondSrc command
gabrielsferre Jul 1, 2024
71a1c49
Creating jump instructions and removing BasicBlocks jump properties
gabrielsferre Jul 1, 2024
dceb559
Taking away the "return" from i.r. printing
gabrielsferre Jul 3, 2024
17e8540
Making fixes for PR
gabrielsferre Jul 4, 2024
b61fc37
Having only one jump per basic block
gabrielsferre Jul 5, 2024
bd591f7
Replacing JmpIfFalse with JmpIf command
gabrielsferre Jul 6, 2024
57c1837
Prettifying generated C code
gabrielsferre Jul 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions spec/execution_tests.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2318,6 +2318,14 @@ function execution_tests.run(compile_file, backend, _ENV, only_compile)
end
end

function m.non_breaking_loop2(): integer
local i = 1
repeat
if i == 42 then return i end
i = i + 1
until false
end

function m.initialize_inside_loop(): integer
local x: integer
repeat
Expand Down
55 changes: 55 additions & 0 deletions spec/uninitialized_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,61 @@ describe("Uninitialized variable analysis: ", function()
]], "variable 'x' is used before being initialized")
end)

it("catches use of uninitialized variable inside \"if\"", function()
assert_error([[
local m: module = {}
function m.foo()
local x:boolean
if x then end
end
return m
]], "variable 'x' is used before being initialized")
end)

it("catches use of uninitialized variable inside \"while\"", function()
assert_error([[
local m: module = {}
function m.foo()
local x:boolean
while x do end
end
return m
]], "variable 'x' is used before being initialized")
end)

it("catches use of uninitialized variable inside \"repeat until\"", function()
assert_error([[
local m: module = {}
function m.foo()
local x:boolean
repeat until x
end
return m
]], "variable 'x' is used before being initialized")
end)

it("catches use of uninitialized variable inside \"for\"", function()
assert_error([[
local m: module = {}
function m.foo()
local x:integer
for i = 1, x, x do end
end
return m
]], "variable 'x' is used before being initialized")
end)

it("catches use of uninitialized variable inside \"for in\"", function()
assert_error([[
local m: module = {}
function m.foo()
local x:{integer}
for i,j in ipairs(x) do end
end
return m
]], "variable 'x' is used before being initialized")
end)

it("catches use of uninitialized upvalue", function ()
assert_error([[
local m = {}
Expand Down
193 changes: 88 additions & 105 deletions src/pallene/coder.lua
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,10 @@ function Coder:c_value(value)
end
end

function Coder:c_label(index)
return "L" .. index
gabrielsferre marked this conversation as resolved.
Show resolved Hide resolved
end

-- The information for creating a C local var for a given Pallene local var.
function Coder:prepare_local_var(func, v_id)
local c_name = self:c_var(v_id)
Expand Down Expand Up @@ -458,7 +462,7 @@ function Coder:pallene_entry_point_definition(f_id)
local slots_needed = max_frame_size + self.max_lua_call_stack_usage[func]

local setline = ""
local void_frameexit = ""
local frameexit = ""
if self.flags.use_traceback then
table.insert(prologue, util.render([[
/**/
Expand All @@ -469,10 +473,7 @@ function Coder:pallene_entry_point_definition(f_id)
}));

setline = string.format("PALLENE_SETLINE(%d);", func.loc and func.loc.line or 0)

if #func.typ.ret_types == 0 then
void_frameexit = "PALLENE_FRAMEEXIT();"
end
frameexit = "PALLENE_FRAMEEXIT();"
end

if slots_needed > 0 then
Expand All @@ -490,7 +491,6 @@ function Coder:pallene_entry_point_definition(f_id)
table.insert(prologue, self:savestack())
table.insert(prologue, "/**/")


for v_id = #arg_types + 1, #func.vars do
-- To avoid -Wmaybe-uninitialized warnings we have to initialize our local variables of type
-- "Any". Nils and Booleans only set the type tag of the TValue and leave the "._value"
Expand All @@ -502,22 +502,44 @@ function Coder:pallene_entry_point_definition(f_id)
table.insert(prologue, decl..initializer..";"..comment)
end

local body = self:generate_cmd(func, func.body)
local body = self:generate_blocks(func)

local ret_mult = ""
local ret = ""
if #func.ret_vars > 0 then
-- We assign the dsts from right to left, in order to match Lua's semantics when a
-- destination variable appears more than once in the LHS. For example, in `x,x = f()`.
-- For a more in-depth discussion, see the implementation of ast.Stat.Assign in to_ir.lua
local returns = {}
for i = #func.ret_vars, 2, -1 do
local var = self:c_var(func.ret_vars[i])
table.insert(returns,
util.render([[ *$reti = $v; ]], { reti = self:c_ret_var(i), v = var }))
end
ret_mult = table.concat(returns, "\n")

local var1 = self:c_var(func.ret_vars[1])
ret = "return " .. var1 .. ";"
end

return (util.render([[
${name_comment}
${fun_decl} {
${prologue}
/**/
${body}
${void_fe}
${ret_mult}
${frameexit}
${ret}
}
]], {
name_comment = C.comment(name_comment),
fun_decl = self:pallene_entry_point_declaration(f_id),
prologue = table.concat(prologue, "\n"),
body = body,
void_fe = void_frameexit
ret_mult = ret_mult,
frameexit = frameexit,
ret = ret,
}))
end

Expand Down Expand Up @@ -725,7 +747,7 @@ function Coder:init_upvalues()

-- String Literals
for _, func in ipairs(self.module.functions) do
for cmd in ir.iter(func.body) do
for cmd in ir.iter(func.blocks) do
gabrielsferre marked this conversation as resolved.
Show resolved Hide resolved
for _, v in ipairs(ir.get_srcs(cmd)) do
if v._tag == "ir.Value.String" then
local str = v.value
Expand Down Expand Up @@ -929,7 +951,7 @@ function Coder:init_gc()

for _, func in ipairs(self.module.functions) do
local max = 0
for cmd in ir.iter(func.body) do
for cmd in ir.iter(func.blocks) do
if cmd._tag == "ir.Cmd.CallDyn" then
-- Although in the end the fn call leaves only ndst items in
-- the stack, we actually need ndst+1 to appease the apicheck
Expand Down Expand Up @@ -1648,130 +1670,91 @@ end
-- Control flow
--

gen_cmd["Nop"] = function(self, _cmd, _func)
return ""
end

gen_cmd["Seq"] = function(self, cmd, func)
local out = {}
for _, c in ipairs(cmd.cmds) do
table.insert(out, self:generate_cmd(func, c))
end
return table.concat(out, "\n")
end

gen_cmd["Return"] = function(self, cmd)
local frameexit = ""
if self.flags.use_traceback then
frameexit = "PALLENE_FRAMEEXIT();"
end

if #cmd.srcs == 0 then
return util.render([[ ${fexit}
return; ]], { fexit = frameexit })
else
-- We assign the dsts from right to left, in order to match Lua's semantics when a
-- destination variable appears more than once in the LHS. For example, in `x,x = f()`.
-- For a more in-depth discussion, see the implementation of ast.Stat.Assign in to_ir.lua
local returns = {}
for i = #cmd.srcs, 2, -1 do
local src = self:c_value(cmd.srcs[i])
table.insert(returns,
util.render([[ *$reti = $v; ]], { reti = self:c_ret_var(i), v = src }))
end
local src1 = self:c_value(cmd.srcs[1])
table.insert(returns, util.render([[ ${fexit}
return $v; ]], { fexit = frameexit, v = src1 }))
return table.concat(returns, "\n")
end
end

gen_cmd["Break"] = function(self, _cmd, _func)
return [[ break; ]]
end

gen_cmd["If"] = function(self, cmd, func)
local condition = self:c_value(cmd.src_condition)
local then_ = self:generate_cmd(func, cmd.then_)
local else_ = self:generate_cmd(func, cmd.else_)

local A = (then_ ~= "")
local B = (else_ ~= "")

local tmpl
if A and (not B) then
tmpl = [[
if ($condition) {
${then_}
}
]]
elseif (not A) and B then
tmpl = [[
if (!$condition) {
${else_}
}
]]
gen_cmd["InitFor"] = function(self, cmd, func)
gabrielsferre marked this conversation as resolved.
Show resolved Hide resolved
local typ = func.vars[cmd.dst_i].typ
local macro
if typ._tag == "types.T.Integer" then
macro = "PALLENE_INT_INIT_FOR_LOOP"
elseif typ._tag == "types.T.Float" then
macro = "PALLENE_FLT_INIT_FOR_LOOP"
else
tmpl = [[
if ($condition) {
${then_}
} else {
${else_}
}
]]
tagged_union.error(typ._tag)
end

return util.render(tmpl, {
condition = condition,
then_ = then_,
else_ = else_,
})
end

gen_cmd["Loop"] = function(self, cmd, func)
local body = self:generate_cmd(func, cmd.body)
return (util.render([[
while (1) {
${body}
}
${macro}($i, $cond, $iter, $count, $start, $limit, $step)
]], {
body = body
macro = macro,
i = self:c_var(cmd.dst_i),
cond = self:c_var(cmd.dst_cond),
iter = self:c_var(cmd.dst_iter),
count = self:c_var(cmd.dst_count),
start = self:c_value(cmd.src_start),
limit = self:c_value(cmd.src_limit),
step = self:c_value(cmd.src_step),
}))
end

gen_cmd["For"] = function(self, cmd, func)
local typ = func.vars[cmd.dst].typ
gen_cmd["IterFor"] = function(self, cmd, func)
local typ = func.vars[cmd.dst_i].typ
gabrielsferre marked this conversation as resolved.
Show resolved Hide resolved

local macro
if typ._tag == "types.T.Integer" then
macro = "PALLENE_INT_FOR_LOOP"
macro = "PALLENE_INT_ITER_FOR_LOOP"
elseif typ._tag == "types.T.Float" then
macro = "PALLENE_FLT_FOR_LOOP"
macro = "PALLENE_FLT_ITER_FOR_LOOP"
else
tagged_union.error(typ._tag)
end

return (util.render([[
${macro}_BEGIN($x, $start, $limit, $step)
{
$body
}
${macro}_END
${macro}($i, $cond, $iter, $count, $start, $limit, $step)
]], {
macro = macro,
x = self:c_var(cmd.dst),
i = self:c_var(cmd.dst_i),
cond = self:c_var(cmd.dst_cond),
iter = self:c_var(cmd.dst_iter),
count = self:c_var(cmd.dst_count),
start = self:c_value(cmd.src_start),
limit = self:c_value(cmd.src_limit),
step = self:c_value(cmd.src_step),
body = self:generate_cmd(func, cmd.body)
}))
end

gen_cmd["Jmp"] = function(self, cmd, _func)
local jmp = "goto " .. self:c_label(cmd.target) .. ";"
return jmp
gabrielsferre marked this conversation as resolved.
Show resolved Hide resolved
end

gen_cmd["JmpIfFalse"] = function(self, cmd, _func)
local jmp_cond = util.render("if(!($v)) {goto $l;}", {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the parens around $v?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not now, but this way changes to the possible values of $v would probably still work.

v = self:c_value(cmd.src_cond),
l = self:c_label(cmd.target),
})
return jmp_cond
end

gen_cmd["CheckGC"] = function(self, cmd, func)
return util.render([[ luaC_condGC(L, ${update_stack_top}, (void)0); ]], {
update_stack_top = self:update_stack_top(func, cmd) })
end

function Coder:generate_blocks(func)
local out = ""
for block_id,block in ipairs(func.blocks) do
-- putting "(void)0" at the label so the C compiler doesn't complain about dangling labels
local content = self:c_label(block_id) .. ":(void)0;\n"
gabrielsferre marked this conversation as resolved.
Show resolved Hide resolved
for _,cmd in ipairs(block.cmds) do
if cmd._tag ~= "ir.Cmd.Jmp" or cmd.target ~= block_id + 1 then
local cmd_str = self:generate_cmd(func, cmd) .. "\n"
content = content .. cmd_str .. "\n"
gabrielsferre marked this conversation as resolved.
Show resolved Hide resolved
end
end
out = out .. content
end
return out
end

gabrielsferre marked this conversation as resolved.
Show resolved Hide resolved
function Coder:generate_cmd(func, cmd)
assert(tagged_union.typename(cmd._tag) == "ir.Cmd")
local name = tagged_union.consname(cmd._tag)
Expand Down
Loading
Loading