Skip to content

Commit

Permalink
Refactor of flow analysis API
Browse files Browse the repository at this point in the history
  • Loading branch information
gabrielsferre committed Sep 3, 2024
1 parent dea83dd commit d955b16
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 146 deletions.
246 changes: 118 additions & 128 deletions src/pallene/flow.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,89 +7,77 @@
--
-- The flow.lua file is designed as an API that helps doing flow analysis.
--
-- Flow Analysis introduction
--
-- We give a brief introduction of flow analysis just to get the reader acquainted with the
-- terminology being used here. To better understand the code you should know how flow analys work
-- already.
--
-- Flow Analysis introduction:
--
-- Doing flow analysis on a function consists of tracking down properties of it's code along each
-- command. These properties are represented using a set. Each basic block has a "start" set and a
-- "finish" set. The "start" set is the set of values available right before we start to process the
-- block's commands and the "finish" set is the set of values available right after we finish
-- processing the commands. Each block also has a "kill" and a "gen" set that help transform the
-- "start" set into the "finish" set. The "kill" set contains the values that will be removed from
-- the running set while "gen" (as in "generate") contains the values that will be added to it. The
-- flow analysis algorithm's input is a pair of "kill" and "gen" sets for each block and the initial
-- values for the "start" sets of each block. During it's runtime, the algorithm updates the "start"
-- and "finish" sets in a loop until they all converge to some value. The algorithm requires a loop
-- because a block's "start" set depends on the "finish" set of it's predecessor or it's successors,
-- depending on what order the flow analysis is being done (some analyses require that we walk
-- through the code backwards).
--
-- processing the block's commands. Each block also has a "kill" and a "gen" set that help transform
-- the "start" set into the "finish" set. The "kill" set contains the values that will be removed
-- from the running set while "gen" (as in "generate") contains the values that will be added to it.
-- The flow analysis algorithm's input is a collection of "kill" and "gen" sets for each block and
-- the initial values for the "start" sets of each block. During it's runtime, the algorithm updates
-- the "start" and "finish" sets in a loop until they all converge to some value. The algorithm
-- requires a loop because a block's "start" set depends on the "finish" set of it's predecessors or
-- it's successors, depending on what order the flow analysis is being done (some analyses require
-- that we walk through the code backwards).
--
-- API usage:
--
-- When doing flow analysis, follow these steps. Also look at examples of usage inside the codebase,
-- as in "gc.lua".
--
-- 1) Create a function of type "function (set, block id)" that will be used internally by the API
-- to initialize the "start" sets. The function is called once for each basic block. It's first
-- argument is the "start" set of the block and the second is the block's index.
-- 1) Create a function of type "function (set, block id)" that will be used for initializing the
-- "start" sets. The function is called once for each basic block. It's first argument is the
-- "start" set of the block and the second is the block's index.
--
-- 2) Create function of type "function (FlowState, block id, command id)" that will be used for
-- updating the running set as we read the function's commands. The first argument is an object
-- of type FlowState, that stores various sets used internally; the second argument is the
-- block index and the third is the command's index inside the block. For removing/adding
-- elements from/to the set, use the API function "flow.kill_value" for removal
-- and "flow.gen_value" for insertion. Both functions are of type "function (FlowState,
-- element)", where the first argument is a FlowState object and the second is the element
-- that will be removed/inserted from/into the set.
-- 2) Create function of type "function (block_id, command_id) -> flow.GenKill" that will be
-- used for updating the running set as we read the function's commands. The first argument is the
-- block index and the second is the command's index inside the block. For indicating elements
-- which elements should be inserted/removed into/from the set, create a new flow.GenKill object
-- and then call the API functions "flow.kill_value" for removal and "flow.gen_value" for
-- insertion. Both functions are of type "function (flow.GenKill, element)", where the first
-- argument is a GenKill object and the second is the element that will be removed/inserted
-- from/into the set. The function must return the flow.GenKill object that you created.
--
-- 3) Create a FlowInfo object
-- The object's constructor takes three parameters:
-- order:
-- The order in which commands and blocks are iterated during flow analysis.
-- "Order.Forwards" updates the running set by reading a block's commands in order and
-- builds the "start" set of a block from it's predecessors' "finish" sets, while
-- "Order.Backwards" updates the running set by reading a block's commands in backwards
-- order and builds the "start" set of a block from it's successors' "finish" sets.
-- process_cmd:
-- function used to update the running set, use the one you wrote in step 2
-- "Order.Forwards" updates the running set by reading a block's commands in order the
-- running set in forwards order and builds the "start" set of a block from it's
-- predecessors' "finish" sets, while "Order.Backwards" updates the running set by
-- reading a block's commands in backwards order and builds the "start" set of a block
-- from it's successors' "finish" sets.
-- compute_gen_kill:
-- function used to update the running set, as mentioned in step 2
-- init_start:
-- function used to initalize the "start" set of blocks, use the one you wrote in
-- step 1
-- function used to initalize the "start" set of blocks, as mentioned in step 1
--
-- 4) Call the function "flow.flow_analysis". It's parameters are:
-- func_block:
-- A list of the function's blocks
-- flow_info :
-- An object of type "FlowInfo". Use the one you created in step 3.
-- a list of the function's blocks
-- flow_info:
-- an object of type "FlowInfo". Use the one you created in step 3.
--
-- "flow.flow_analysis" returns a list of objects of type "FlowState.Build". Each basic block has a
-- corresponding object on the list.
-- "flow.flow_analysis" returns a list that contains a set for each block in the function. The
-- returned sets are the starting sets of each corresponding block. To get the sets corresponding
-- to the commands of a block, you'll have to loop through them and update the set yourself. The
-- next step teaches you how to do that.
--
-- 5) Having now the list of flow states, iterate through blocks and commands (if you used
-- Order.Backwards previously, in step 3, then you'll have to iterate through the commands of the
-- block backwards too).
-- 5) Having now the list sets, iterate through blocks and commands (if you used Order.Backwards
-- previously, in step 3, then you'll have to iterate through the commands of the block backwards
-- too).
--
-- 5.1) Inside the loop that iterates over blocks and before entering the loop that iterates over
-- the commands of a block, call "flow.make_apply_state". The function receives one argument,
-- which will be the flow state of the current block that can retrieved from the list obtained in
-- step 4 (e.g. the flow state corresponding to the 3rd block will be flow_state_list[3]).
-- "flow.make_apply_state" returns an object of type "FlowState.Apply". This one will be used to
-- update the state of the flow analysis set as we iterate over the commands of the current
-- block. This set can be accesses through the "set" property of the "FlowState.Apply" object
-- returned by "flow.make_apply_state". Checking the contents of this set as you update it
-- throught the commands is essentialy the whole point of everything we're doing here, that's
-- what flow analysis is for. The "set" property of the "FlowState.Apply" object returned by
-- "flow.make_apply_state" is equal the "start" set of the current block.
--
-- 5.2) Inside the loop that iterates over a block's commands, call "flow.update_set" to update
-- the set of the "FlowState.Apply" object. "flow.update_set"'s first argument is the
-- "FlowState.Apply" object; second argument is the FlowInfo object created in step 3; third
-- argument is the current block's index and the forth argument is the current command's index
-- inside the block.
-- 5.1) Inside the loop that iterates over a block's commands, call "flow.update_set" to update
-- the set. "flow.update_set"'s first argument is the set that will be updated; second argument
-- is the FlowInfo object created in step 3; third argument is the current block's index and the
-- forth argument is the current command's index inside the block.

local flow = {}

Expand All @@ -102,40 +90,35 @@ define_union("Order", {
Backwards = {},
})

define_union("FlowState", {
Build = {
"start", -- set of values when we start analysing the block
"finish", -- set of values when we finish analysing the block,
"kill", -- set of values
"gen", -- set of values
},

Apply = {
"set", -- set of values
},
})
function flow.GenKill()
return {
kill = {}, -- set of values
gen = {}, -- set of values
}
end


function flow.FlowInfo(order, process_cmd, init_start)
local function FlowState()
return {
order = order, -- flow.Order
process_cmd = process_cmd, -- function
init_start = init_start, -- function
start = {}, -- set of values when we start analysing the block
finish = {}, -- set of values when we finish analysing the block,
gk = flow.GenKill(),
}
end

local function copy_set(S)
local new_set = {}
for v,_ in pairs(S) do
new_set[v] = true
end
return new_set
function flow.FlowInfo(order, compute_gen_kill, init_start)
return {
order = order, -- flow.Order
compute_gen_kill = compute_gen_kill, -- function (block_id, cmd_id) -> flow.GenKill
init_start = init_start, -- function (set, block_id) -> void
}
end

local function apply_gen_kill_sets(flow_state)
local start = flow_state.start
local finish = flow_state.finish
local gen = flow_state.gen
local kill = flow_state.kill
local gen = flow_state.gk.gen
local kill = flow_state.gk.kill
local in_changed = false

for v, _ in pairs(start) do
Expand Down Expand Up @@ -173,52 +156,68 @@ local function clear_set(S)
end
end

local function merge_sets(blocks_states, src_indices, dest_index)
local dest = blocks_states[dest_index].start
local function merge_sets(state_list, src_indices, dest_index)
local dest = state_list[dest_index].start
clear_set(dest)
for _,src_i in ipairs(src_indices) do
local src = blocks_states[src_i].finish
local src = state_list[src_i].finish
for v, _ in pairs(src) do
dest[v] = true
end
end
end

local function apply_cmd_gk_to_block_gk(cmd_gk, block_gk)
local cmd_gen = cmd_gk.gen
local cmd_kill = cmd_gk.kill
local block_gen = block_gk.gen
local block_kill = block_gk.kill
for v,_ in pairs(cmd_gen) do
assert(not cmd_kill[v], "cmd_gen and cmd_kill must not intersect")
block_gen[v] = true
block_kill[v] = nil
end
for v,_ in pairs(cmd_kill) do
assert(not cmd_gen[v], "cmd_gen and cmd_kill must not intersect")
block_gen[v] = nil
block_kill[v] = true
end
end

local function make_state_list(block_list, flow_info)
local blocks_states = {}
local state_list = {}
local order = flow_info.order._tag
for block_i, block in ipairs(block_list) do
local state = flow.FlowState.Build({},{},{},{})
blocks_states[block_i] = state
flow_info.init_start(state.start, block_i)
local block_state = FlowState()
state_list[block_i] = block_state
flow_info.init_start(block_state.start, block_i)
if order == "flow.Order.Forward" then
for cmd_i = 1, #block.cmds do
flow_info.process_cmd(state, block_i, cmd_i)
local cmd_gk = flow_info.compute_gen_kill(block_i, cmd_i)
apply_cmd_gk_to_block_gk(cmd_gk, block_state.gk)
end
elseif order == "flow.Order.Backwards" then
for cmd_i = #block.cmds, 1, -1 do
flow_info.process_cmd(state, block_i, cmd_i)
local cmd_gk = flow_info.compute_gen_kill(block_i, cmd_i)
apply_cmd_gk_to_block_gk(cmd_gk, block_state.gk)
end
else
tagged_union.error(order)
end
end
return blocks_states
return state_list
end

function flow.flow_analysis(block_list, flow_info)

local blocks_states = make_state_list(block_list, flow_info)
local state_list = make_state_list(block_list, flow_info)

local succ_list = ir.get_successor_list(block_list)
local pred_list = ir.get_predecessor_list(block_list)

local block_order -- { block_id }, order in which blocks will be traversed
local merge_src_list -- { block_id => { block_id } }, maps a block to the blocks it uses
-- for assembling it's "start" set
local dirty_propagation_list -- { block_id => { block_id } }, maps a block to the blocks it
-- should propagate the dirty flag when it's "finish" set is
-- changed
local block_order
local merge_src_list
local dirty_propagation_list
local order = flow_info.order._tag
if order == "flow.Order.Forward" then
block_order = ir.get_successor_depth_search_topological_sort(succ_list)
Expand All @@ -240,12 +239,12 @@ function flow.flow_analysis(block_list, flow_info)
local first_block_i = block_order[1]

local function update_block(block_i)
local state = blocks_states[block_i]
local state = state_list[block_i]

-- first block's starting set is supposed to be constant
if block_i ~= first_block_i then
local src_indices = merge_src_list[block_i]
merge_sets(blocks_states, src_indices, block_i)
merge_sets(state_list, src_indices, block_i)
end

local dirty_propagation = dirty_propagation_list[block_i]
Expand All @@ -271,43 +270,34 @@ function flow.flow_analysis(block_list, flow_info)
end
until not found_dirty_block

return blocks_states
end

function flow.kill_value(flow_state, value)
local tag = flow_state._tag
if tag == "flow.FlowState.Build" then
flow_state.kill[value] = true
flow_state.gen[value] = nil
elseif tag == "flow.FlowState.Apply" then
flow_state.set[value] = nil
else
tagged_union.error(tag)
local block_start_list = {}
for state_i, flow_state in ipairs(state_list) do
block_start_list[state_i] = flow_state.start
end

return block_start_list
end

function flow.gen_value(flow_state, value)
local tag = flow_state._tag
if tag == "flow.FlowState.Build" then
flow_state.gen[value] = true
flow_state.kill[value] = nil
elseif tag == "flow.FlowState.Apply" then
flow_state.set[value] = true
else
tagged_union.error(tag)
function flow.update_set(set, flow_info, block_i, cmd_i)
local gk = flow_info.compute_gen_kill(block_i, cmd_i)
for v,_ in pairs(gk.gen) do
assert(not gk.kill[v], "gen and kill must not intersect")
set[v] = true
end
for v,_ in pairs(gk.kill) do
assert(not gk.gen[v], "gen and kill must not intersect")
set[v] = nil
end
end

function flow.make_apply_state(flow_state)
assert(flow_state._tag == "flow.FlowState.Build")
local start = copy_set(flow_state.start)
local a_state = flow.FlowState.Apply(start)
return a_state
function flow.gen_value(gen_kill, v)
gen_kill.gen[v] = true
gen_kill.kill[v] = nil
end

function flow.update_set(flow_state, flow_info, block_i, cmd_i)
assert(flow_state._tag == "flow.FlowState.Apply")
flow_info.process_cmd(flow_state, block_i, cmd_i)
function flow.kill_value(gen_kill, v)
gen_kill.gen[v] = nil
gen_kill.kill[v] = true
end

return flow
Loading

0 comments on commit d955b16

Please sign in to comment.