local stage = {}
|
|
-- The main part of analysis is connecting assignments to locals or upvalues
|
-- with accesses that may use the assigned value.
|
-- Accesses and assignments are split into two groups based on whether they happen
|
-- in the closure that defines subject local variable (main assignment, main access)
|
-- or in some nested closure (closure assignment, closure access).
|
-- To avoid false positives, it's assumed that a closure may be called at any point
|
-- starting from expression that creates it.
|
-- Additionally, all operations on upvalues are considered in bulk, as in,
|
-- when a closure is called, it's assumed that any subset of its upvalue assignments
|
-- and accesses may happen, in any order.
|
|
-- Assignments and accesses are connected based on whether they can reach each other.
|
-- A main assignment is connected with a main access when the assignment can reach the access.
|
-- A main assignment is connected with a closure access when the assignment can reach the closure creation
|
-- or the closure creation can reach the assignment.
|
-- A closure assignment is connected with a main access when the closure creation can reach the access.
|
-- A closure assignment is connected with a closure access when either closure creation can reach the other one.
|
|
-- To determine what flow graph nodes an assignment or a closure creation can reach,
|
-- they are independently propagated along the graph.
|
-- Closure creation propagation is not bounded.
|
-- Main assignment propagation is bounded by entrance and exit conditions for each reached flow graph node.
|
-- Entrance condition checks that target local variable is still in scope. If entrance condition fails,
|
-- nothing in the node can refer to the variable, and the scope can't be reentered later.
|
-- So, in this case, assignment does not reach the node, and propagation does not continue.
|
-- Exit condition checks that target local variable is not overwritten by an assignment in the node.
|
-- If it fails, the assignment still reaches the node (because all accesses in a node are evaluated before any
|
-- assignments take effect), but propagation does not continue.
|
|
local function register_value(values_per_var, var, value)
|
if not values_per_var[var] then
|
values_per_var[var] = {}
|
end
|
|
table.insert(values_per_var[var], value)
|
end
|
|
-- Called when assignment of `value` is connected to an access.
|
-- `item` contains the access, and `line` contains the item.
|
local function add_resolution(line, item, var, value, is_mutation)
|
register_value(item.used_values, var, value)
|
value[is_mutation and "mutated" or "used"] = true
|
value.using_lines[line] = true
|
|
if value.secondaries then
|
value.secondaries.used = true
|
end
|
end
|
|
-- Connects accesses in given items array with an assignment of `value`.
|
-- `items` may be `nil` instead of empty.
|
local function add_resolutions(line, items, var, value, is_mutation)
|
if not items then
|
return
|
end
|
|
for _, item in ipairs(items) do
|
add_resolution(line, item, var, value, is_mutation)
|
end
|
end
|
|
-- Connects all accesses (and mutations) in `access_line` with corresponding
|
-- assignments in `set_line`.
|
local function cross_resolve_closures(access_line, set_line)
|
for var, setting_items in pairs(set_line.set_upvalues) do
|
for _, setting_item in ipairs(setting_items) do
|
add_resolutions(access_line, access_line.accessed_upvalues[var],
|
var, setting_item.set_variables[var])
|
add_resolutions(access_line, access_line.mutated_upvalues[var],
|
var, setting_item.set_variables[var], true)
|
end
|
end
|
end
|
|
local function in_scope(var, index)
|
return (var.scope_start <= index) and (index <= var.scope_end)
|
end
|
|
-- Called when main assignment propagation reaches a line item.
|
local function main_assignment_propagation_callback(line, index, item, var, value)
|
-- Check entrance condition.
|
if not in_scope(var, index) then
|
-- Assignment reaches the end of variable scope, so it can't be dominated by any assignment.
|
value.overwriting_item = false
|
return true
|
end
|
|
-- Assignment reaches this item, apply its effect.
|
|
-- Accesses (and mutations) of the variable can resolve to reaching assignment.
|
if item.accesses and item.accesses[var] then
|
add_resolution(line, item, var, value)
|
end
|
|
if item.mutations and item.mutations[var] then
|
add_resolution(line, item, var, value, true)
|
end
|
|
-- Accesses (and mutations) of the variable inside closures created in this item
|
-- can resolve to reaching assignment.
|
if item.lines then
|
for _, created_line in ipairs(item.lines) do
|
add_resolutions(created_line, created_line.accessed_upvalues[var], var, value)
|
add_resolutions(created_line, created_line.mutated_upvalues[var], var, value, true)
|
end
|
end
|
|
-- Check exit condition.
|
if item.set_variables and item.set_variables[var] then
|
if value.overwriting_item ~= false then
|
if value.overwriting_item and value.overwriting_item ~= item then
|
value.overwriting_item = false
|
else
|
value.overwriting_item = item
|
end
|
end
|
|
return true
|
end
|
end
|
|
-- Connects main assignments with main accesses and closure accesses in reachable closures.
|
-- Additionally, sets `overwriting_item` field of values to an item with an assignment overwriting
|
-- the value, but only if the overwriting is not avoidable (i.e. it's impossible to reach end of function
|
-- from the first assignment without going through the second one). Otherwise value of the field may be
|
-- `false` or `nil`.
|
local function propagate_main_assignments(line)
|
for i, item in ipairs(line.items) do
|
if item.set_variables then
|
for var, value in pairs(item.set_variables) do
|
if var.line == line then
|
-- Assignments are not live at their own item, because assignments take effect only after all accesses
|
-- are evaluated. Items with assignments can't be jumps, so they have a single following item
|
-- with incremented index.
|
line:walk({}, i + 1, main_assignment_propagation_callback, var, value)
|
end
|
end
|
end
|
end
|
end
|
|
-- Called when closure creation propagation reaches a line item.
|
local function closure_creation_propagation_callback(line, _, item, propagated_line)
|
if not item then
|
return true
|
end
|
|
-- Closure creation reaches this item, apply its effects.
|
|
-- Accesses (and mutations) of upvalues in the propagated closure
|
-- can resolve to assignments in the item.
|
if item.set_variables then
|
for var, value in pairs(item.set_variables) do
|
add_resolutions(propagated_line, propagated_line.accessed_upvalues[var], var, value)
|
add_resolutions(propagated_line, propagated_line.mutated_upvalues[var], var, value, true)
|
end
|
end
|
|
if item.lines then
|
for _, created_line in ipairs(item.lines) do
|
-- Accesses (and mutations) of upvalues in the propagated closure
|
-- can resolve to assignments in closures created in the item.
|
cross_resolve_closures(propagated_line, created_line)
|
|
-- Accesses (and mutations) of upvalues in closures created in the item
|
-- can resolve to assignments in the propagated closure.
|
cross_resolve_closures(created_line, propagated_line)
|
end
|
end
|
|
-- Accesses (and mutations) of locals in the item can resolve
|
-- to assignments in the propagated closure.
|
for var, setting_items in pairs(propagated_line.set_upvalues) do
|
if item.accesses and item.accesses[var] then
|
for _, setting_item in ipairs(setting_items) do
|
add_resolution(line, item, var, setting_item.set_variables[var])
|
end
|
end
|
|
if item.mutations and item.mutations[var] then
|
for _, setting_item in ipairs(setting_items) do
|
add_resolution(line, item, var, setting_item.set_variables[var], true)
|
end
|
end
|
end
|
end
|
|
-- Connects main assignments with closure accesses in reaching closures.
|
-- Connects closure assignments with main accesses and with closure accesses in reachable closures.
|
-- Connects closure accesses with closure assignments in reachable closures.
|
local function propagate_closure_creations(line)
|
for i, item in ipairs(line.items) do
|
if item.lines then
|
for _, created_line in ipairs(item.lines) do
|
-- Closures are live at the item they are created, as they can be called immediately.
|
line:walk({}, i, closure_creation_propagation_callback, created_line)
|
end
|
end
|
end
|
end
|
|
local function analyze_line(line)
|
propagate_main_assignments(line)
|
propagate_closure_creations(line)
|
end
|
|
-- Finds reaching assignments for all local variable accesses.
|
function stage.run(chstate)
|
for _, line in ipairs(chstate.lines) do
|
analyze_line(line)
|
end
|
end
|
|
return stage
|