diff --git a/src/pallene/flow.lua b/src/pallene/flow.lua index 0b61a5c1..4bdaaa18 100644 --- a/src/pallene/flow.lua +++ b/src/pallene/flow.lua @@ -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 = {} @@ -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 @@ -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) @@ -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] @@ -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 diff --git a/src/pallene/gc.lua b/src/pallene/gc.lua index 44b3770d..ae82abe5 100644 --- a/src/pallene/gc.lua +++ b/src/pallene/gc.lua @@ -66,13 +66,14 @@ local function compute_stack_slots(func) end end - local function process_cmd(flow_state, block_i, cmd_i) + local function compute_gen_kill(block_i, cmd_i) local cmd = func.blocks[block_i].cmds[cmd_i] assert(tagged_union.typename(cmd._tag) == "ir.Cmd") + local gk = flow.GenKill() for _, dst in ipairs(ir.get_dsts(cmd)) do local typ = func.vars[dst].typ if types.is_gc(typ) then - flow.kill_value(flow_state, dst) + flow.kill_value(gk, dst) end end @@ -80,14 +81,15 @@ local function compute_stack_slots(func) if src._tag == "ir.Value.LocalVar" then local typ = func.vars[src.id].typ if types.is_gc(typ) then - flow.gen_value(flow_state, src.id) + flow.gen_value(gk, src.id) end end end + return gk end - local flow_info = flow.FlowInfo(flow.Order.Backwards, process_cmd, init_start) - local blocks_flow_states = flow.flow_analysis(func.blocks, flow_info) + local flow_info = flow.FlowInfo(flow.Order.Backwards, compute_gen_kill, init_start) + local sets_list = flow.flow_analysis(func.blocks, flow_info) -- 2) Find which GC'd variables are live at each GC spot in the program and -- which GC'd variables are live at the same time @@ -104,18 +106,18 @@ local function compute_stack_slots(func) end for block_i, block in ipairs(func.blocks) do - local lives_block = flow.make_apply_state(blocks_flow_states[block_i]) + local lives_block = sets_list[block_i] for cmd_i = #block.cmds, 1, -1 do local cmd = block.cmds[cmd_i] flow.update_set(lives_block, flow_info, block_i, cmd_i) if cmd_uses_gc(cmd) then local lives_cmd = {} - for var,_ in pairs(lives_block.set) do + for var,_ in pairs(lives_block) do table.insert(lives_cmd, var) end live_gc_vars[block_i][cmd_i] = lives_cmd - for var1,_ in pairs(lives_block.set) do - for var2,_ in pairs(lives_block.set) do + for var1,_ in pairs(lives_block) do + for var2,_ in pairs(lives_block) do if not live_at_same_time[var1] then live_at_same_time[var1] = {} end diff --git a/src/pallene/uninitialized.lua b/src/pallene/uninitialized.lua index 189be72b..1bb23e08 100644 --- a/src/pallene/uninitialized.lua +++ b/src/pallene/uninitialized.lua @@ -31,15 +31,16 @@ function uninitialized.verify_variables(module) end end - local function process_cmd(flow_state, block_i, cmd_i) + local function compute_gen_kill(block_i, cmd_i) local cmd = func.blocks[block_i].cmds[cmd_i] + local gk = flow.GenKill() for _, src in ipairs(ir.get_srcs(cmd)) do if src._tag == "ir.Value.LocalVar" then -- `SetField` instructions can count as initializers when the target is an -- upvalue box. This is because upvalue boxes are allocated, but not initialized -- upon declaration. if cmd._tag == "ir.Cmd.SetField" and cmd.rec_typ.is_upvalue_box then - flow.kill_value(flow_state, src.id) + flow.kill_value(gk, src.id) end end end @@ -47,25 +48,25 @@ function uninitialized.verify_variables(module) -- Artificial initializers introduced by the compilers do not count. if not (cmd._tag == "ir.Cmd.NewRecord" and cmd.rec_typ.is_upvalue_box) then for _, v_id in ipairs(ir.get_dsts(cmd)) do - flow.kill_value(flow_state, v_id) + flow.kill_value(gk, v_id) end end + return gk end - local flow_info = flow.FlowInfo(flow.Order.Forward, process_cmd, init_start) - local blocks_flow_states = flow.flow_analysis(func.blocks, flow_info) + local flow_info = flow.FlowInfo(flow.Order.Forward, compute_gen_kill, init_start) + local sets_list = flow.flow_analysis(func.blocks, flow_info) -- check for errors local reported_variables = {} -- (only one error message per variable) for block_i, block in ipairs(func.blocks) do - local flow_state = blocks_flow_states[block_i] - local uninit = flow.make_apply_state(flow_state) + local uninit = sets_list[block_i] for cmd_i, cmd in ipairs(block.cmds) do local loc = cmd.loc flow.update_set(uninit, flow_info, block_i, cmd_i) for _, src in ipairs(ir.get_srcs(cmd)) do local v = src.id - if src._tag == "ir.Value.LocalVar" and uninit.set[v] then + if src._tag == "ir.Value.LocalVar" and uninit[v] then if not reported_variables[v] then reported_variables[v] = true local name = assert(func.vars[v].name) @@ -77,7 +78,7 @@ function uninitialized.verify_variables(module) end end - local exit_uninit = blocks_flow_states[#func.blocks].finish + local exit_uninit = sets_list[#func.blocks] if #func.ret_vars > 0 then local ret1 = func.ret_vars[1] if exit_uninit[ret1] then