From c225a2ee1e8b0f1421283732a6c0e1128ef0bb86 Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Wed, 17 Jul 2024 21:29:33 -0300 Subject: [PATCH 1/5] Using flow analysis for the gc's liveness analysis --- src/pallene/gc.lua | 222 ++++++++++++++++++++++++++++++++------------- 1 file changed, 158 insertions(+), 64 deletions(-) diff --git a/src/pallene/gc.lua b/src/pallene/gc.lua index 8d11d042..f3dd7b07 100644 --- a/src/pallene/gc.lua +++ b/src/pallene/gc.lua @@ -5,6 +5,7 @@ local ir = require "pallene.ir" local types = require "pallene.types" +local tagged_union = require "pallene.tagged_union" -- GARBAGE COLLECTION -- ================== @@ -18,8 +19,8 @@ local types = require "pallene.types" -- have already been saved by the caller. -- -- As an optimization, we don't save values to the Lua stack if the associated variable dies before --- it reaches a potential garbage collection site. The current analysis is pretty simple, and there --- are many ways to make it more precise. So we don't forget, I'm listing some of the ideas here... +-- it reaches a potential garbage collection site. The current implementation uses flow analysis to +-- find live variables. So we don't forget, I'm listing here some ideas to improve the analysis ... -- But it should be said that we don't know if implementing them would be worth the trouble. -- -- 1) Insert fewer checkGC calls in our functions, or move the checkGC calls to places with fewer @@ -28,77 +29,168 @@ local types = require "pallene.types" -- 2) Identify functions that don't call the GC (directly or indirectly) and don't treat calls to -- them as potential GC sites. (Function inlining might mitigate this for small functions) -- --- 3) Use a flow-based liveliness analysis to precisely identify the commands that a variable --- appears live at, instead of approximating with first definition and last use. --- --- 4) Use SSA form or some form of reaching definitions analysis so that we we only need to mirror +-- 3) Use SSA form or some form of reaching definitions analysis so that we we only need to mirror -- the writes that reach a GC site, instead of always mirroring all writes to a variable if one -- of them reaches a GC site. local gc = {} -function gc.compute_stack_slots(func) +local function flow_analysis(block_list, live_sets, gen_sets, kill_sets) + local function merge_live(A, B, gen_set, kill_set) + local changed = false + for v, _ in pairs(B) do + local val = true + if kill_set[v] then + val = nil + end + local previous_val = A[v] + local new_val = previous_val or val + A[v] = new_val + changed = changed or (previous_val ~= new_val) + end + for v, gen in pairs(gen_set) do + assert(gen ~= true or gen ~= kill_set[v], "gen and kill can't both be true") + local previous_val = A[v] + local new_val = true + A[v] = new_val + changed = changed or (previous_val ~= new_val) + end + return changed + end - local flat_cmds = ir.flatten_cmd(func.blocks) + local pred_list = ir.get_predecessor_list(block_list) + local block_order = ir.get_predecessor_depth_search_topological_sort(pred_list) + + local function block_analysis(block_i) + local block_preds = pred_list[block_i] + local live = live_sets[block_i] + local gen = gen_sets[block_i] + local kill = kill_sets[block_i] + local changed = false + for _,pred in ipairs(block_preds) do + local c = merge_live(live_sets[pred], live, gen, kill) + changed = c or changed + end + return changed + end - -- 1) Compute approximated live intervals for GC variables defined by the function. Function - -- parameters are only counted if they are redefined, since their original value was already - -- saved by the caller. Also note that we only care about variables, not about upvalues. - -- The latter are already exposed to the GC via the function closures. + repeat + local changed = false + for _,block_i in ipairs(block_order) do + changed = block_analysis(block_i) or changed + end + until not changed +end - local defined_variables = {} -- { var_id }, sorted by first definition - local last_use = {} -- { var_id => integer } - local first_definition = {} -- { var_id => integer } +local function mark_gen_kill(cmd, gen_set, kill_set) + assert(tagged_union.typename(cmd._tag) == "ir.Cmd") + for _, dst in ipairs(ir.get_dsts(cmd)) do + gen_set[dst] = nil + kill_set[dst] = true + end - for i, cmd in ipairs(flat_cmds) do - for _, val in ipairs(ir.get_srcs(cmd)) do - if val._tag == "ir.Value.LocalVar" then - local v_id = val.id - last_use[v_id] = i - end + for _, src in ipairs(ir.get_srcs(cmd)) do + if src._tag == "ir.Value.LocalVar" then + gen_set[src.id] = true + kill_set[src.id] = nil end - for _, v_id in ipairs(ir.get_dsts(cmd)) do - local typ = func.vars[v_id].typ - if types.is_gc(typ) and not first_definition[v_id] then - first_definition[v_id] = i - table.insert(defined_variables, v_id) - end + end +end + +local function make_gen_kill_sets(block) + local gen = {} + local kill = {} + for i = #block.cmds, 1, -1 do + local cmd = block.cmds[i] + mark_gen_kill(cmd, gen, kill) + end + return gen, kill +end + +function gc.compute_stack_slots(func) + -- initialize sets + + -- variables with values that turn live in a given block w.r.t flow entering the block + local gen_sets = {} -- { block_id -> { var_id -> bool? } } + + -- variables with values that are killed in a given block w.r.t flow entering the block + local kill_sets = {} -- { block_id -> { var_id -> bool? } } + + -- variables with values that are live at the end of a given block + local live_sets = {} -- { block_id -> { var_id -> bool? } } + + for _,b in ipairs(func.blocks) do + local gen, kill = make_gen_kill_sets(b) + table.insert(kill_sets, kill) + table.insert(gen_sets, gen) + table.insert(live_sets, {}) + end + + -- set returned variables to "live" on exit block + if #func.blocks > 0 then + local exit_live_set = live_sets[#func.blocks] + for _, var in ipairs(func.ret_vars) do + exit_live_set[var] = true end end - -- 2) Find which variables are live at each GC spot in the program. + -- 1) Find live variables at the end of each basic block + flow_analysis(func.blocks, live_sets, gen_sets, kill_sets) + -- 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 local live_gc_vars = {} -- { cmd => {var_id}? } - for i, cmd in ipairs(flat_cmds) do - local tag = cmd._tag - if - tag == "ir.Cmd.CallStatic" or - tag == "ir.Cmd.CallDyn" or - tag == "ir.Cmd.CheckGC" - then - live_gc_vars[cmd] = {} - for _, v_id in ipairs(defined_variables) do - local a = first_definition[v_id] - local b = last_use[v_id] - if a and b and a < i and i <= b then - table.insert(live_gc_vars[cmd], v_id) - end + local live_at_same_time = {} -- { var_id => { var_id => bool? }? } + + for block_i, block in ipairs(func.blocks) do + local lives_block = live_sets[block_i] + -- filter out non-GC'd variables from set + for var_i, _ in pairs(lives_block) do + local var = func.vars[var_i] + if not types.is_gc(var.typ) then + lives_block[var_i] = nil end end - end + for cmd_i = #block.cmds, 1, -1 do + local cmd = block.cmds[cmd_i] + assert(tagged_union.typename(cmd._tag) == "ir.Cmd") + for _, dst in ipairs(ir.get_dsts(cmd)) do + lives_block[dst] = nil + end + for _, src in ipairs(ir.get_srcs(cmd)) do + if src._tag == "ir.Value.LocalVar" then + local typ = func.vars[src.id].typ + if types.is_gc(typ) then + lives_block[src.id] = true + end + end + end - local variable_is_live_at_gc = {} -- { var_id => boolean } - for v_id = 1, #func.vars do - variable_is_live_at_gc[v_id] = false - end - for _, v_ids in pairs(live_gc_vars) do - for _, v_id in ipairs(v_ids) do - variable_is_live_at_gc[v_id] = true + local tag = cmd._tag + if tag == "ir.Cmd.CallStatic" or + tag == "ir.Cmd.CallDyn" or + tag == "ir.Cmd.CheckGC" + then + local lives_cmd = {} + for var,_ in pairs(lives_block) do + table.insert(lives_cmd, var) + end + live_gc_vars[cmd] = lives_cmd + 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 + live_at_same_time[var1][var2] = true + end + end + end end end -- 3) Allocate variables to Lua stack slots, ensuring that variables with overlapping lifetimes - -- different stack slots. IMPORTANT: stack slots are 0-based. The C we generate prefers that. + -- get different stack slots. IMPORTANT: stack slots are 0-based. The C we generate prefers + -- that. local max_frame_size = 0 local slot_of_variable = {} -- { var_id => integer? } @@ -107,21 +199,22 @@ function gc.compute_stack_slots(func) slot_of_variable[v_id] = false end - local n = 0 - local stack = { } -- { var_id } - for _, v_id in ipairs(defined_variables) do - if variable_is_live_at_gc[v_id] then - local def = first_definition[v_id] - while n > 0 and last_use[stack[n]] <= def do - stack[n] = nil - n = n - 1 + for v1, set in pairs(live_at_same_time) do + local taken_slots = {} -- { stack_slot => bool? } + for v2,_ in pairs(set) do + local v2_slot = slot_of_variable[v2] + if v2_slot then + taken_slots[v2_slot] = true end - - n = n + 1 - slot_of_variable[v_id] = n-1 - stack[n] = v_id - max_frame_size = math.max(max_frame_size, n) end + for slot = 0, #func.vars do + if not taken_slots[slot] then + slot_of_variable[v1] = slot + max_frame_size = math.max(max_frame_size, slot + 1) + break + end + end + assert(slot_of_variable[v1], "should always find a slot") end return { @@ -131,4 +224,5 @@ function gc.compute_stack_slots(func) } end + return gc From 9a4af27541b15fb1a2a42955872ce878aaa6c01e Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Sat, 20 Jul 2024 21:04:04 -0300 Subject: [PATCH 2/5] Change slot_of_variable table so it doesn't use commands as keys --- src/pallene/coder.lua | 431 +++++++++++++++++++++--------------------- src/pallene/gc.lua | 25 ++- 2 files changed, 237 insertions(+), 219 deletions(-) diff --git a/src/pallene/coder.lua b/src/pallene/coder.lua index 19ae5c75..70d26ce1 100644 --- a/src/pallene/coder.lua +++ b/src/pallene/coder.lua @@ -984,10 +984,10 @@ end -- the stack top before a GC point so the GC can look at the right set of variables. We also need -- to do it before function calls because the stack-gowing logic relies on having the right "top". -function Coder:update_stack_top(func, cmd) +local function update_stack_top(gc_info) local offset = 0 - for _, v_id in ipairs(self.gc[func].live_gc_vars[cmd]) do - local slot = self.gc[func].slot_of_variable[v_id] + for _, v_id in ipairs(gc_info.live_gc_vars) do + local slot = gc_info.slot_of_variable[v_id] offset = math.max(offset, slot + 1) end return util.render("L->top.p = base + $offset;", { offset = C.integer(offset) }) @@ -1007,15 +1007,15 @@ end local gen_cmd = {} -gen_cmd["Move"] = function(self, cmd, _func) - local dst = self:c_var(cmd.dst) - local src = self:c_value(cmd.src) +gen_cmd["Move"] = function(self, st) + local dst = self:c_var(st.cmd.dst) + local src = self:c_value(st.cmd.src) return (util.render([[ $dst = $src; ]], { dst = dst, src = src })) end -gen_cmd["Unop"] = function(self, cmd, _func) - local dst = self:c_var(cmd.dst) - local x = self:c_value(cmd.src) +gen_cmd["Unop"] = function(self, st) + local dst = self:c_var(st.cmd.dst) + local x = self:c_value(st.cmd.src) -- For when we can directly translate to a C operator: local function unop(op) @@ -1033,8 +1033,8 @@ gen_cmd["Unop"] = function(self, cmd, _func) ${check_no_metatable} $dst = luaH_getn($x); ]], { - check_no_metatable = check_no_metatable(self, x, cmd.loc), - line = C.integer(cmd.loc.line), + check_no_metatable = check_no_metatable(self, x, st.cmd.loc), + line = C.integer(st.cmd.loc.line), dst = dst, x = x })) @@ -1045,7 +1045,7 @@ gen_cmd["Unop"] = function(self, cmd, _func) dst = dst, x = x })) end - local op = cmd.op + local op = st.cmd.op if op == "ArrLen" then return arr_len() elseif op == "StrLen" then return str_len() elseif op == "IntNeg" then return int_neg() @@ -1057,10 +1057,10 @@ gen_cmd["Unop"] = function(self, cmd, _func) end end -gen_cmd["Binop"] = function(self, cmd, _func) - local dst = self:c_var(cmd.dst) - local x = self:c_value(cmd.src1) - local y = self:c_value(cmd.src2) +gen_cmd["Binop"] = function(self, st) + local dst = self:c_var(st.cmd.dst) + local x = self:c_value(st.cmd.src1) + local y = self:c_value(st.cmd.src2) -- For when we can be directly translate to a C operator: local function binop(op) @@ -1082,7 +1082,7 @@ gen_cmd["Binop"] = function(self, cmd, _func) -- For integer division and modulus: local function int_division(fname) - local line = cmd.loc.line + local line = st.cmd.loc.line return (util.render([[ $dst = $fname(L, $x, $y, PALLENE_SOURCE_FILE, $line); ]], { fname = fname, dst = dst, @@ -1124,7 +1124,7 @@ gen_cmd["Binop"] = function(self, cmd, _func) dst = dst, x = x, y = y, op = op })) end - local op = cmd.op + local op = st.cmd.op if op == "IntAdd" then return int_binop("+") elseif op == "IntSub" then return int_binop("-") elseif op == "IntMul" then return int_binop("*") @@ -1180,11 +1180,11 @@ gen_cmd["Binop"] = function(self, cmd, _func) end end -gen_cmd["Concat"] = function(self, cmd, _func) - local dst = self:c_var(cmd.dst) +gen_cmd["Concat"] = function(self, st) + local dst = self:c_var(st.cmd.dst) local init_input_array = {} - for ix, srcv in ipairs(cmd.srcs) do + for ix, srcv in ipairs(st.cmd.srcs) do local src = self:c_value(srcv) table.insert(init_input_array, util.render([[ ss[$i] = $src; ]], { @@ -1201,60 +1201,60 @@ gen_cmd["Concat"] = function(self, cmd, _func) } ]], { dst = dst, - N = C.integer(#cmd.srcs), + N = C.integer(#st.cmd.srcs), init_input_array = table.concat(init_input_array, "\n"), })) end -gen_cmd["ToFloat"] = function(self, cmd, _func) - local dst = self:c_var(cmd.dst) - local v = self:c_value(cmd.src) +gen_cmd["ToFloat"] = function(self, st) + local dst = self:c_var(st.cmd.dst) + local v = self:c_value(st.cmd.src) return util.render([[ $dst = (lua_Number) $v; ]], { dst = dst, v = v }) end -gen_cmd["ToDyn"] = function(self, cmd, _func) - local dst = self:c_var(cmd.dst) - local src = self:c_value(cmd.src) - local src_typ = cmd.src_typ +gen_cmd["ToDyn"] = function(self, st) + local dst = self:c_var(st.cmd.dst) + local src = self:c_value(st.cmd.src) + local src_typ = st.cmd.src_typ return (set_stack_slot(src_typ, "&"..dst, src)) end -gen_cmd["FromDyn"] = function(self, cmd, _func) - local dst = self:c_var(cmd.dst) - local src = self:c_value(cmd.src) - local dst_typ = cmd.dst_typ +gen_cmd["FromDyn"] = function(self, st) + local dst = self:c_var(st.cmd.dst) + local src = self:c_value(st.cmd.src) + local dst_typ = st.cmd.dst_typ return self:get_stack_slot(dst_typ, dst, "&"..src, - cmd.loc, "downcasted value") + st.cmd.loc, "downcasted value") end -gen_cmd["IsTruthy"] = function(self, cmd, _func) - local dst = self:c_var(cmd.dst) - local src = self:c_value(cmd.src) +gen_cmd["IsTruthy"] = function(self, st) + local dst = self:c_var(st.cmd.dst) + local src = self:c_value(st.cmd.src) return (util.render([[ $dst = pallene_is_truthy(&$src); ]], { dst = dst, src = src })) end -gen_cmd["IsNil"] = function(self, cmd, _func) - local dst = self:c_var(cmd.dst) - local src = self:c_value(cmd.src) +gen_cmd["IsNil"] = function(self, st) + local dst = self:c_var(st.cmd.dst) + local src = self:c_value(st.cmd.src) return (util.render([[ $dst = ttisnil(&$src); ]], { dst = dst, src = src })) end -gen_cmd["NewArr"] = function(self, cmd, _func) - local dst = self:c_var(cmd.dst) - local n = self:c_value(cmd.src_size) +gen_cmd["NewArr"] = function(self, st) + local dst = self:c_var(st.cmd.dst) + local n = self:c_value(st.cmd.src_size) return (util.render([[ $dst = pallene_createtable(L, $n, 0); ]], { dst = dst, n = n, })) end -gen_cmd["GetArr"] = function(self, cmd, _func) - local dst = self:c_var(cmd.dst) - local arr = self:c_value(cmd.src_arr) - local i = self:c_value(cmd.src_i) - local dst_typ = cmd.dst_typ - local line = C.integer(cmd.loc.line) +gen_cmd["GetArr"] = function(self, st) + local dst = self:c_var(st.cmd.dst) + local arr = self:c_value(st.cmd.src_arr) + local i = self:c_value(st.cmd.src_i) + local dst_typ = st.cmd.dst_typ + local line = C.integer(st.cmd.loc.line) return (util.render([[ { @@ -1267,16 +1267,16 @@ gen_cmd["GetArr"] = function(self, cmd, _func) i = i, line = line, get_slot = self:get_luatable_slot(dst_typ, dst, "slot", arr, - cmd.loc, "array element"), + st.cmd.loc, "array element"), })) end -gen_cmd["SetArr"] = function(self, cmd, _func) - local arr = self:c_value(cmd.src_arr) - local i = self:c_value(cmd.src_i) - local v = self:c_value(cmd.src_v) - local src_typ = cmd.src_typ - local line = C.integer(cmd.loc.line) +gen_cmd["SetArr"] = function(self, st) + local arr = self:c_value(st.cmd.src_arr) + local i = self:c_value(st.cmd.src_i) + local v = self:c_value(st.cmd.src_v) + local src_typ = st.cmd.src_typ + local line = C.integer(st.cmd.loc.line) return (util.render([[ { pallene_renormalize_array(L, $arr, $i, PALLENE_SOURCE_FILE, $line); @@ -1292,23 +1292,23 @@ gen_cmd["SetArr"] = function(self, cmd, _func) })) end -gen_cmd["NewTable"] = function(self, cmd, _func) - local dst = self:c_var(cmd.dst) - local n = self:c_value(cmd.src_size) +gen_cmd["NewTable"] = function(self, st) + local dst = self:c_var(st.cmd.dst) + local n = self:c_value(st.cmd.src_size) return (util.render([[ $dst = pallene_createtable(L, 0, $n); ]], { dst = dst, n = n, })) end -gen_cmd["GetTable"] = function(self, cmd, _func) - local dst = self:c_var(cmd.dst) - local tab = self:c_value(cmd.src_tab) - local key = self:c_value(cmd.src_k) - local dst_typ = cmd.dst_typ +gen_cmd["GetTable"] = function(self, st) + local dst = self:c_var(st.cmd.dst) + local tab = self:c_value(st.cmd.src_tab) + local key = self:c_value(st.cmd.src_k) + local dst_typ = st.cmd.dst_typ - assert(cmd.src_k._tag == "ir.Value.String") - local field_name = cmd.src_k.value + assert(st.cmd.src_k._tag == "ir.Value.String") + local field_name = st.cmd.src_k.value return util.render([[ { @@ -1320,18 +1320,18 @@ gen_cmd["GetTable"] = function(self, cmd, _func) field_len = tostring(#field_name), tab = tab, key = key, - get_slot = self:get_luatable_slot(dst_typ, dst, "slot", tab, cmd.loc, "table field"), + get_slot = self:get_luatable_slot(dst_typ, dst, "slot", tab, st.cmd.loc, "table field"), }) end -gen_cmd["SetTable"] = function(self, cmd, _func) - local tab = self:c_value(cmd.src_tab) - local key = self:c_value(cmd.src_k) - local val = self:c_value(cmd.src_v) - local src_typ = cmd.src_typ +gen_cmd["SetTable"] = function(self, st) + local tab = self:c_value(st.cmd.src_tab) + local key = self:c_value(st.cmd.src_k) + local val = self:c_value(st.cmd.src_v) + local src_typ = st.cmd.src_typ - assert(cmd.src_k._tag == "ir.Value.String") - local field_name = cmd.src_k.value + assert(st.cmd.src_k._tag == "ir.Value.String") + local field_name = st.cmd.src_k.value return util.render([[ { @@ -1360,22 +1360,22 @@ gen_cmd["SetTable"] = function(self, cmd, _func) }) end -gen_cmd["NewRecord"] = function(self, cmd, _func) - local rc = self.record_coders[cmd.rec_typ] - local rec = self:c_var(cmd.dst) +gen_cmd["NewRecord"] = function(self, st) + local rc = self.record_coders[st.cmd.rec_typ] + local rec = self:c_var(st.cmd.dst) return (util.render([[$rec = $constructor(L, K);]] , { rec = rec, constructor = rc:constructor_name(), })) end -gen_cmd["GetField"] = function(self, cmd, _func) - local rec_typ = cmd.rec_typ +gen_cmd["GetField"] = function(self, st) + local rec_typ = st.cmd.rec_typ local rc = self.record_coders[rec_typ] - local dst = self:c_var(cmd.dst) - local rec = self:c_value(cmd.src_rec) - local field_name = cmd.field_name + local dst = self:c_var(st.cmd.dst) + local rec = self:c_value(st.cmd.src_rec) + local field_name = st.cmd.field_name local f_typ = rec_typ.field_types[field_name] if types.is_gc(f_typ) then @@ -1391,13 +1391,13 @@ gen_cmd["GetField"] = function(self, cmd, _func) end end -gen_cmd["SetField"] = function(self, cmd, _func) - local rec_typ = cmd.rec_typ +gen_cmd["SetField"] = function(self, st) + local rec_typ = st.cmd.rec_typ local rc = self.record_coders[rec_typ] - local rec = self:c_value(cmd.src_rec) - local v = self:c_value(cmd.src_v) - local field_name = cmd.field_name + local rec = self:c_value(st.cmd.src_rec) + local v = self:c_value(st.cmd.src_v) + local field_name = st.cmd.field_name local f_typ = rec_typ.field_types[field_name] if types.is_gc(f_typ) then @@ -1409,8 +1409,8 @@ gen_cmd["SetField"] = function(self, cmd, _func) end end -gen_cmd["NewClosure"] = function (self, cmd, _func) - local func = self.module.functions[cmd.f_id] +gen_cmd["NewClosure"] = function (self, st) + local func = self.module.functions[st.cmd.f_id] -- The number of upvalues must fit inside a byte (the nupvalues in the ClosureHeader). -- However, we must check this limit ourselves, because luaF_newCclosure doesn't. If we have too @@ -1427,19 +1427,19 @@ gen_cmd["NewClosure"] = function (self, cmd, _func) } ]], { num_upvalues = C.integer(num_upvalues), - dst = self:c_var(cmd.dst), - lua_entry_point = self:lua_entry_point_name(cmd.f_id), + dst = self:c_var(st.cmd.dst), + lua_entry_point = self:lua_entry_point_name(st.cmd.f_id), }) end -gen_cmd["InitUpvalues"] = function(self, cmd, _func) - local func = self.module.functions[cmd.f_id] +gen_cmd["InitUpvalues"] = function(self, st) + local func = self.module.functions[st.cmd.f_id] - assert(cmd.src_f._tag == "ir.Value.LocalVar") - local cclosure = string.format("clCvalue(&%s)", self:c_var(cmd.src_f.id)) + assert(st.cmd.src_f._tag == "ir.Value.LocalVar") + local cclosure = string.format("clCvalue(&%s)", self:c_var(st.cmd.src_f.id)) local capture_upvalues = {} - for i, val in ipairs(cmd.srcs) do + for i, val in ipairs(st.cmd.srcs) do local typ = func.captured_vars[i].typ local c_val = self:c_value(val) local upvalue_dst = string.format("&(ccl->upvalue[%s])", C.integer(i)) @@ -1463,35 +1463,35 @@ gen_cmd["InitUpvalues"] = function(self, cmd, _func) }) end -gen_cmd["CallStatic"] = function(self, cmd, func) +gen_cmd["CallStatic"] = function(self, st) local dsts = {} - for i, dst in ipairs(cmd.dsts) do + for i, dst in ipairs(st.cmd.dsts) do dsts[i] = dst and self:c_var(dst) end local xs = {} - for _, x in ipairs(cmd.srcs) do + for _, x in ipairs(st.cmd.srcs) do table.insert(xs, self:c_value(x)) end local parts = {} - local f_val = cmd.src_f + local f_val = st.cmd.src_f local f_id, cclosure if f_val._tag == "ir.Value.Upvalue" then - f_id = assert(func.f_id_of_upvalue[f_val.id]) + f_id = assert(st.func.f_id_of_upvalue[f_val.id]) cclosure = string.format("clCvalue(&%s)", self:c_value(f_val)) elseif f_val._tag == "ir.Value.LocalVar" then - f_id = assert(func.f_id_of_local[f_val.id]) + f_id = assert(st.func.f_id_of_local[f_val.id]) cclosure = string.format("clCvalue(&%s)", self:c_value(f_val)) else tagged_union.error(f_val._tag) end - table.insert(parts, self:update_stack_top(func, cmd)) + table.insert(parts, update_stack_top(st.gc_info)) if self.flags.use_traceback then table.insert(parts, string.format("PALLENE_SETLINE(%d);\n", - func.loc and func.loc.line or 0)) + st.func.loc and st.func.loc.line or 0)) end table.insert(parts, self:call_pallene_function(dsts, f_id, cclosure, xs, nil)) @@ -1499,24 +1499,24 @@ gen_cmd["CallStatic"] = function(self, cmd, func) return table.concat(parts, "\n") end -gen_cmd["CallDyn"] = function(self, cmd, func) - local f_typ = cmd.f_typ +gen_cmd["CallDyn"] = function(self, st) + local f_typ = st.cmd.f_typ local dsts = {} - for i, dst in ipairs(cmd.dsts) do + for i, dst in ipairs(st.cmd.dsts) do dsts[i] = dst and self:c_var(dst) end local push_arguments = {} - table.insert(push_arguments, self:push_to_stack(f_typ, self:c_value(cmd.src_f))) + table.insert(push_arguments, self:push_to_stack(f_typ, self:c_value(st.cmd.src_f))) for i = 1, #f_typ.arg_types do local typ = f_typ.arg_types[i] - table.insert(push_arguments, self:push_to_stack(typ, self:c_value(cmd.srcs[i]))) + table.insert(push_arguments, self:push_to_stack(typ, self:c_value(st.cmd.srcs[i]))) end local pop_results = {} for i = #f_typ.ret_types, 1, -1 do local typ = f_typ.ret_types[i] - local get_slot = self:get_stack_slot(typ, dsts[i], "slot", cmd.loc, "return value #%d", i) + local get_slot = self:get_stack_slot(typ, dsts[i], "slot", st.cmd.loc, "return value #%d", i) table.insert(pop_results, util.render([[ { L->top.p--; @@ -1531,7 +1531,7 @@ gen_cmd["CallDyn"] = function(self, cmd, func) local setline = "" if self.flags.use_traceback then setline = util.render([[ PALLENE_SETLINE($line); ]], { - line = C.integer(func.loc and func.loc.line or 0) + line = C.integer(st.func.loc and st.func.loc.line or 0) }) end @@ -1543,7 +1543,7 @@ gen_cmd["CallDyn"] = function(self, cmd, func) ${pop_results} ${restore_stack} ]], { - update_stack_top = self:update_stack_top(func, cmd), + update_stack_top = update_stack_top(st.gc_info), push_arguments = table.concat(push_arguments, "\n"), setline = setline, pop_results = table.concat(pop_results, "\n"), @@ -1553,43 +1553,43 @@ gen_cmd["CallDyn"] = function(self, cmd, func) }) end -gen_cmd["BuiltinIoWrite"] = function(self, cmd, _func) - local v = self:c_value(cmd.srcs[1]) +gen_cmd["BuiltinIoWrite"] = function(self, st) + local v = self:c_value(st.cmd.srcs[1]) return util.render([[ pallene_io_write(L, $v); ]], { v = v }) end -gen_cmd["BuiltinMathAbs"] = function(self, cmd, _func) - local dst = self:c_var(cmd.dsts[1]) - local v = self:c_value(cmd.srcs[1]) +gen_cmd["BuiltinMathAbs"] = function(self, st) + local dst = self:c_var(st.cmd.dsts[1]) + local v = self:c_value(st.cmd.srcs[1]) return util.render([[ $dst = l_mathop(fabs)($v); ]], { dst = dst, v = v }) end -gen_cmd["BuiltinMathCeil"] = function(self, cmd, _func) - local dst = self:c_var(cmd.dsts[1]) - local v = self:c_value(cmd.srcs[1]) - local line = cmd.loc.line +gen_cmd["BuiltinMathCeil"] = function(self, st) + local dst = self:c_var(st.cmd.dsts[1]) + local v = self:c_value(st.cmd.srcs[1]) + local line = st.cmd.loc.line return util.render([[ $dst = pallene_math_ceil(L, PALLENE_SOURCE_FILE, $line, $v); ]], { dst = dst, v = v, line = C.integer(line) }) end -gen_cmd["BuiltinMathFloor"] = function(self, cmd, _func) - local dst = self:c_var(cmd.dsts[1]) - local v = self:c_value(cmd.srcs[1]) - local line = cmd.loc.line +gen_cmd["BuiltinMathFloor"] = function(self, st) + local dst = self:c_var(st.cmd.dsts[1]) + local v = self:c_value(st.cmd.srcs[1]) + local line = st.cmd.loc.line return util.render([[ $dst = pallene_math_floor(L, PALLENE_SOURCE_FILE, $line, $v); ]], { dst = dst, v = v, line = C.integer(line) }) end -gen_cmd["BuiltinMathFmod"] = function(self, cmd, _func) - local dst = self:c_var(cmd.dsts[1]) - local x = self:c_value(cmd.srcs[1]) - local y = self:c_value(cmd.srcs[2]) +gen_cmd["BuiltinMathFmod"] = function(self, st) + local dst = self:c_var(st.cmd.dsts[1]) + local x = self:c_value(st.cmd.srcs[1]) + local y = self:c_value(st.cmd.srcs[2]) return util.render([[ $dst = l_mathop(fmod)($x, $y); ]], { dst = dst, x = x, y = y }) end -gen_cmd["BuiltinMathExp"] = function(self, cmd, _func) - local dst = self:c_var(cmd.dsts[1]) - local v = self:c_value(cmd.srcs[1]) +gen_cmd["BuiltinMathExp"] = function(self, st) + local dst = self:c_var(st.cmd.dsts[1]) + local v = self:c_value(st.cmd.srcs[1]) return util.render([[ $dst = l_mathop(exp)($v); ]], { dst = dst, v = v }) end @@ -1599,69 +1599,69 @@ end -- But for --emit-lua, we must do something to make the code work in pure Lua. -- For now, the easiest thing to do is inject math.ln = math.log at the top. -- A smarter routine would replace math.ln with math.log. -gen_cmd["BuiltinMathLn"] = function(self, cmd, _func) - local dst = self:c_var(cmd.dsts[1]) - local v = self:c_value(cmd.srcs[1]) +gen_cmd["BuiltinMathLn"] = function(self, st) + local dst = self:c_var(st.cmd.dsts[1]) + local v = self:c_value(st.cmd.srcs[1]) return util.render([[ $dst = l_mathop(log)($v); ]], { dst = dst, v = v }) end -gen_cmd["BuiltinMathLog"] = function(self, cmd, _func) - local dst = self:c_var(cmd.dsts[1]) - local v = self:c_value(cmd.srcs[1]) - local b = self:c_value(cmd.srcs[2]) +gen_cmd["BuiltinMathLog"] = function(self, st) + local dst = self:c_var(st.cmd.dsts[1]) + local v = self:c_value(st.cmd.srcs[1]) + local b = self:c_value(st.cmd.srcs[2]) return util.render([[ $dst = pallene_math_log($v, $b); ]], { dst = dst, v = v, b = b }) end -gen_cmd["BuiltinMathModf"] = function(self, cmd, _func) - local dst1 = self:c_var(cmd.dsts[1]) - local dst2 = self:c_var(cmd.dsts[2]) - local v = self:c_value(cmd.srcs[1]) - local line = cmd.loc.line +gen_cmd["BuiltinMathModf"] = function(self, st) + local dst1 = self:c_var(st.cmd.dsts[1]) + local dst2 = self:c_var(st.cmd.dsts[2]) + local v = self:c_value(st.cmd.srcs[1]) + local line = st.cmd.loc.line return util.render([[ $dst1 = pallene_math_modf(L, PALLENE_SOURCE_FILE, $line, $v, &$dst2); ]], { dst1 = dst1, dst2 = dst2, v = v, line = C.integer(line) }) end -gen_cmd["BuiltinMathPow"] = function(self, cmd, _func) - local dst = self:c_var(cmd.dsts[1]) - local x = self:c_value(cmd.srcs[1]) - local y = self:c_value(cmd.srcs[2]) +gen_cmd["BuiltinMathPow"] = function(self, st) + local dst = self:c_var(st.cmd.dsts[1]) + local x = self:c_value(st.cmd.srcs[1]) + local y = self:c_value(st.cmd.srcs[2]) return util.render([[ $dst = l_mathop(pow)($x, $y); ]], { dst = dst, x = x, y = y }) end -gen_cmd["BuiltinMathSqrt"] = function(self, cmd, _func) - local dst = self:c_var(cmd.dsts[1]) - local v = self:c_value(cmd.srcs[1]) +gen_cmd["BuiltinMathSqrt"] = function(self, st) + local dst = self:c_var(st.cmd.dsts[1]) + local v = self:c_value(st.cmd.srcs[1]) return util.render([[ $dst = l_mathop(sqrt)($v); ]], { dst = dst, v = v }) end -gen_cmd["BuiltinStringChar"] = function(self, cmd, _func) - local dst = self:c_var(cmd.dsts[1]) - local v = self:c_value(cmd.srcs[1]) - local line = cmd.loc.line +gen_cmd["BuiltinStringChar"] = function(self, st) + local dst = self:c_var(st.cmd.dsts[1]) + local v = self:c_value(st.cmd.srcs[1]) + local line = st.cmd.loc.line return util.render([[ $dst = pallene_string_char(L, PALLENE_SOURCE_FILE, $line, $v); ]], { dst = dst, v = v, line = C.integer(line) }) end -gen_cmd["BuiltinStringSub"] = function(self, cmd, _func) - local dst = self:c_var(cmd.dsts[1]) - local str = self:c_value(cmd.srcs[1]) - local i = self:c_value(cmd.srcs[2]) - local j = self:c_value(cmd.srcs[3]) +gen_cmd["BuiltinStringSub"] = function(self, st) + local dst = self:c_var(st.cmd.dsts[1]) + local str = self:c_value(st.cmd.srcs[1]) + local i = self:c_value(st.cmd.srcs[2]) + local j = self:c_value(st.cmd.srcs[3]) return util.render([[ $dst = pallene_string_sub(L, $str, $i, $j); ]], { dst = dst, str = str, i = i, j = j }) end -gen_cmd["BuiltinType"] = function(self, cmd, _func) - local dst = self:c_var(cmd.dsts[1]) - local v = self:c_value(cmd.srcs[1]) +gen_cmd["BuiltinType"] = function(self, st) + local dst = self:c_var(st.cmd.dsts[1]) + local v = self:c_value(st.cmd.srcs[1]) return util.render([[ $dst = pallene_type_builtin(L, $v); ]], { dst = dst, v = v }) end -gen_cmd["BuiltinTostring"] = function(self, cmd, _func) - local dst = self:c_var(cmd.dsts[1]) - local v = self:c_value(cmd.srcs[1]) - local line = cmd.loc.line +gen_cmd["BuiltinTostring"] = function(self, st) + local dst = self:c_var(st.cmd.dsts[1]) + local v = self:c_value(st.cmd.srcs[1]) + local line = st.cmd.loc.line return util.render([[ $dst = pallene_tostring(L, PALLENE_SOURCE_FILE, $line, $v); ]], { dst = dst, line = C.integer(line), v = v }) end @@ -1670,8 +1670,8 @@ end -- Control flow -- -gen_cmd["ForPrep"] = function(self, cmd, func) - local typ = func.vars[cmd.dst_i].typ +gen_cmd["ForPrep"] = function(self, st) + local typ = st.func.vars[st.cmd.dst_i].typ local macro if typ._tag == "types.T.Integer" then macro = "PALLENE_INT_FOR_PREP" @@ -1685,18 +1685,18 @@ gen_cmd["ForPrep"] = function(self, cmd, func) ${macro}($i, $cond, $iter, $count, $start, $limit, $step) ]], { 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), + i = self:c_var(st.cmd.dst_i), + cond = self:c_var(st.cmd.dst_cond), + iter = self:c_var(st.cmd.dst_iter), + count = self:c_var(st.cmd.dst_count), + start = self:c_value(st.cmd.src_start), + limit = self:c_value(st.cmd.src_limit), + step = self:c_value(st.cmd.src_step), })) end -gen_cmd["ForStep"] = function(self, cmd, func) - local typ = func.vars[cmd.dst_i].typ +gen_cmd["ForStep"] = function(self, st) + local typ = st.func.vars[st.cmd.dst_i].typ local macro if typ._tag == "types.T.Integer" then @@ -1711,62 +1711,67 @@ gen_cmd["ForStep"] = function(self, cmd, func) ${macro}($i, $cond, $iter, $count, $start, $limit, $step) ]], { 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), + i = self:c_var(st.cmd.dst_i), + cond = self:c_var(st.cmd.dst_cond), + iter = self:c_var(st.cmd.dst_iter), + count = self:c_var(st.cmd.dst_count), + start = self:c_value(st.cmd.src_start), + limit = self:c_value(st.cmd.src_limit), + step = self:c_value(st.cmd.src_step), })) end -gen_cmd["Jmp"] = function(self, cmd, _func, block_id) - if cmd.target ~= block_id + 1 then - return "goto " .. self:c_label(cmd.target) .. ";" - else - return "" -- fallthrough - end +gen_cmd["Jmp"] = function(self, st) + return "goto " .. self:c_label(st.cmd.target) .. ";" end -gen_cmd["JmpIf"] = function(self, cmd, _func, block_id) - local template - if cmd.target_false == block_id + 1 then - template = "if ($v) goto $t;" - elseif cmd.target_true == block_id + 1 then - template = "if (!$v) goto $f;" - else - template = "if ($v) goto $t; else goto $f;" - end - return util.render(template, { - v = self:c_value(cmd.src_cond), - t = self:c_label(cmd.target_true), - f = self:c_label(cmd.target_false), +gen_cmd["JmpIf"] = function(self, st) + return util.render("if($v) {goto $t;} else {goto $f;}", { + v = self:c_value(st.cmd.src_cond), + t = self:c_label(st.cmd.target_true), + f = self:c_label(st.cmd.target_false), }) end -gen_cmd["CheckGC"] = function(self, cmd, func) +gen_cmd["CheckGC"] = function(self, st) return util.render([[ luaC_condGC(L, ${update_stack_top}, (void)0); ]], { - update_stack_top = self:update_stack_top(func, cmd) }) + update_stack_top = update_stack_top(st.gc_info) }) end function Coder:generate_blocks(func) local out = {} - for block_id, block in ipairs(func.blocks) do - table.insert(out, self:c_label(block_id)..":\n") - for _,cmd in ipairs(block.cmds) do - table.insert(out, self:generate_cmd(func, block_id, cmd)) - table.insert(out, "\n") + for block_i,block in ipairs(func.blocks) do + table.insert(out, util.render("$label:\n", { + label = self:c_label(block_i), + })) + for cmd_i,cmd in ipairs(block.cmds) do + if cmd._tag ~= "ir.Cmd.Jmp" or cmd.target ~= block_i + 1 then + local gen_state = { + cmd = cmd, + func = func, + gc_info = false, + } + if gc.cmd_uses_gc(cmd._tag) then + gen_state.gc_info = { + live_gc_vars = self.gc[func].live_gc_vars[block_i][cmd_i], + slot_of_variable = self.gc[func].slot_of_variable, + } + end + local cmd_str = self:generate_cmd(gen_state) .. "\n" + table.insert(out, cmd_str) + end end end return table.concat(out) end -function Coder:generate_cmd(func, block_id, cmd) +function Coder:generate_cmd(gen_state) + local cmd = gen_state.cmd + local func = gen_state.func assert(tagged_union.typename(cmd._tag) == "ir.Cmd") local name = tagged_union.consname(cmd._tag) local f = assert(gen_cmd[name], "impossible") - local out = f(self, cmd, func, block_id) + local out = f(self, gen_state) for _, v_id in ipairs(ir.get_dsts(cmd)) do local n = self.gc[func].slot_of_variable[v_id] diff --git a/src/pallene/gc.lua b/src/pallene/gc.lua index f3dd7b07..217fc6d3 100644 --- a/src/pallene/gc.lua +++ b/src/pallene/gc.lua @@ -35,6 +35,13 @@ local tagged_union = require "pallene.tagged_union" local gc = {} +function gc.cmd_uses_gc(tag) + assert(tagged_union.typename(tag) == "ir.Cmd") + return tag == "ir.Cmd.CallStatic" or + tag == "ir.Cmd.CallDyn" or + tag == "ir.Cmd.CheckGC" +end + local function flow_analysis(block_list, live_sets, gen_sets, kill_sets) local function merge_live(A, B, gen_set, kill_set) local changed = false @@ -139,9 +146,18 @@ function gc.compute_stack_slots(func) -- 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 - local live_gc_vars = {} -- { cmd => {var_id}? } + local live_gc_vars = {} -- { block_id => { cmd_id => {var_id}? } } local live_at_same_time = {} -- { var_id => { var_id => bool? }? } + -- initialize live_gc_vars + for _, block in ipairs(func.blocks) do + local live_on_cmds = {} + for cmd_i = 1, #block.cmds do + live_on_cmds[cmd_i] = false + end + table.insert(live_gc_vars, live_on_cmds) + end + for block_i, block in ipairs(func.blocks) do local lives_block = live_sets[block_i] -- filter out non-GC'd variables from set @@ -166,16 +182,13 @@ function gc.compute_stack_slots(func) end end - local tag = cmd._tag - if tag == "ir.Cmd.CallStatic" or - tag == "ir.Cmd.CallDyn" or - tag == "ir.Cmd.CheckGC" + if gc.cmd_uses_gc(cmd._tag) then local lives_cmd = {} for var,_ in pairs(lives_block) do table.insert(lives_cmd, var) end - live_gc_vars[cmd] = lives_cmd + live_gc_vars[block_i][cmd_i] = lives_cmd for var1,_ in pairs(lives_block) do for var2,_ in pairs(lives_block) do if not live_at_same_time[var1] then From 7e924a2ba06005c4b6a809c56c695e690648a9d2 Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Fri, 19 Jul 2024 21:20:48 -0300 Subject: [PATCH 3/5] Using work-list to check flow analysis convergence --- src/pallene/gc.lua | 151 +++++++++++++++++++++++----------- src/pallene/uninitialized.lua | 132 ++++++++++++++++++++--------- 2 files changed, 194 insertions(+), 89 deletions(-) diff --git a/src/pallene/gc.lua b/src/pallene/gc.lua index 217fc6d3..75efcb5d 100644 --- a/src/pallene/gc.lua +++ b/src/pallene/gc.lua @@ -35,6 +35,15 @@ local tagged_union = require "pallene.tagged_union" local gc = {} +local function FlowState() + return { + input = {}, -- {var_id -> bool?} live variables at block start + output = {}, -- {var_id -> bool?} live variables at block end + kill = {}, -- {var_id -> bool?} variables that are killed inside block + gen = {}, -- {var_id -> bool?} variables that become live inside block + } +end + function gc.cmd_uses_gc(tag) assert(tagged_union.typename(tag) == "ir.Cmd") return tag == "ir.Cmd.CallStatic" or @@ -42,51 +51,106 @@ function gc.cmd_uses_gc(tag) tag == "ir.Cmd.CheckGC" end -local function flow_analysis(block_list, live_sets, gen_sets, kill_sets) - local function merge_live(A, B, gen_set, kill_set) - local changed = false - for v, _ in pairs(B) do +local function copy_set(S) + local new_set = {} + for v,_ in pairs(S) do + new_set[v] = true + end + return new_set +end + +local function flow_analysis(block_list, state_list) + local function apply_gen_kill_sets(flow_state) + local input = flow_state.input + local output = flow_state.output + local gen = flow_state.gen + local kill = flow_state.kill + local in_changed = false + + for v, _ in pairs(output) do local val = true - if kill_set[v] then + if kill[v] then val = nil end - local previous_val = A[v] + local previous_val = input[v] local new_val = previous_val or val - A[v] = new_val - changed = changed or (previous_val ~= new_val) + input[v] = new_val + in_changed = in_changed or (previous_val ~= new_val) end - for v, gen in pairs(gen_set) do - assert(gen ~= true or gen ~= kill_set[v], "gen and kill can't both be true") - local previous_val = A[v] + + for v, g in pairs(gen) do + assert(g ~= true or g ~= kill[v], "gen and kill can't both be true") + local previous_val = input[v] local new_val = true - A[v] = new_val - changed = changed or (previous_val ~= new_val) + input[v] = new_val + in_changed = in_changed or (previous_val ~= new_val) + end + + for v, _ in pairs(input) do + if not output[v] and not gen[v] then + input[v] = nil + in_changed = true + end + end + + return in_changed + end + + local function merge_live(input, output) + for v, _ in pairs(input) do + output[v] = true + end + end + + local function empty_set(S) + for v,_ in pairs(S) do + S[v] = nil end - return changed end + local succ_list = ir.get_successor_list(block_list) local pred_list = ir.get_predecessor_list(block_list) local block_order = ir.get_predecessor_depth_search_topological_sort(pred_list) - local function block_analysis(block_i) + local dirty_flag = {} -- { block_id -> bool? } keeps track of modified blocks + for i = 1, #block_list do + dirty_flag[i] = true + end + + local function update_block(block_i) + local block_succs = succ_list[block_i] local block_preds = pred_list[block_i] - local live = live_sets[block_i] - local gen = gen_sets[block_i] - local kill = kill_sets[block_i] - local changed = false - for _,pred in ipairs(block_preds) do - local c = merge_live(live_sets[pred], live, gen, kill) - changed = c or changed + local state = state_list[block_i] + + -- last block's output is supposed to be fixed + if block_i ~= #block_list then + empty_set(state.output) + for _,succ in ipairs(block_succs) do + local succ_in = state_list[succ].input + merge_live(succ_in, state.output) + end + end + + local in_changed = apply_gen_kill_sets(state) + if in_changed then + for _, pred in ipairs(block_preds) do + dirty_flag[pred] = true + end end - return changed end repeat - local changed = false + local found_dirty_block = false for _,block_i in ipairs(block_order) do - changed = block_analysis(block_i) or changed + if dirty_flag[block_i] then + found_dirty_block = true + -- CAREFUL: we have to clean the dirty flag BEFORE updating the block or else we + -- will do the wrong thing for auto-referencing blocks + dirty_flag[block_i] = nil + update_block(block_i) + end end - until not changed + until not found_dirty_block end local function mark_gen_kill(cmd, gen_set, kill_set) @@ -104,45 +168,34 @@ local function mark_gen_kill(cmd, gen_set, kill_set) end end -local function make_gen_kill_sets(block) - local gen = {} - local kill = {} +local function make_gen_kill_sets(block, flow_state) for i = #block.cmds, 1, -1 do local cmd = block.cmds[i] - mark_gen_kill(cmd, gen, kill) + mark_gen_kill(cmd, flow_state.gen, flow_state.kill) end - return gen, kill end function gc.compute_stack_slots(func) - -- initialize sets - -- variables with values that turn live in a given block w.r.t flow entering the block - local gen_sets = {} -- { block_id -> { var_id -> bool? } } + local state_list = {} -- { FlowState } - -- variables with values that are killed in a given block w.r.t flow entering the block - local kill_sets = {} -- { block_id -> { var_id -> bool? } } - - -- variables with values that are live at the end of a given block - local live_sets = {} -- { block_id -> { var_id -> bool? } } - - for _,b in ipairs(func.blocks) do - local gen, kill = make_gen_kill_sets(b) - table.insert(kill_sets, kill) - table.insert(gen_sets, gen) - table.insert(live_sets, {}) + -- initialize states + for block_i, block in ipairs(func.blocks) do + local fst = FlowState() + make_gen_kill_sets(block, fst) + state_list[block_i] = fst end -- set returned variables to "live" on exit block if #func.blocks > 0 then - local exit_live_set = live_sets[#func.blocks] + local exit_output = state_list[#func.blocks].output for _, var in ipairs(func.ret_vars) do - exit_live_set[var] = true + exit_output[var] = true end end -- 1) Find live variables at the end of each basic block - flow_analysis(func.blocks, live_sets, gen_sets, kill_sets) + flow_analysis(func.blocks, state_list) -- 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 @@ -159,7 +212,7 @@ function gc.compute_stack_slots(func) end for block_i, block in ipairs(func.blocks) do - local lives_block = live_sets[block_i] + local lives_block = copy_set(state_list[block_i].output) -- filter out non-GC'd variables from set for var_i, _ in pairs(lives_block) do local var = func.vars[var_i] diff --git a/src/pallene/uninitialized.lua b/src/pallene/uninitialized.lua index d6e42f77..d16cf3da 100644 --- a/src/pallene/uninitialized.lua +++ b/src/pallene/uninitialized.lua @@ -7,15 +7,28 @@ -- initialized and when control flows to the end of a non-void function without returning. Make sure -- that you call ir.clean first, so that it does the right thing in the presence of `while true` -- loops. --- --- `uninit` is the set of variables that are potentially uninitialized. --- `kill` is the set of variables that are initialized at a given block. local ir = require "pallene.ir" local tagged_union = require "pallene.tagged_union" local uninitialized = {} +local function FlowState() + return { + input = {}, -- {var_id -> bool?} uninitialized variables at block start + output = {}, -- {var_id -> bool?} uninitialized variables at block end + kill = {}, -- {var_id -> bool?} variables that are initialized inside block + } +end + +local function copy_set(S) + local new_set = {} + for v,_ in pairs(S) do + new_set[v] = true + end + return new_set +end + local function fill_set(cmd, set, val) assert(tagged_union.typename(cmd._tag) == "ir.Cmd") for _, src in ipairs(ir.get_srcs(cmd)) do @@ -37,41 +50,85 @@ local function fill_set(cmd, set, val) end end -local function flow_analysis(block_list, uninit_sets, kill_sets) - local function merge_uninit(A, B, kill) - local changed = false - for v, _ in pairs(B) do +local function flow_analysis(block_list, state_list) + local function apply_kill_set(flow_state) + local input = flow_state.input + local output = flow_state.output + local kill = flow_state.kill + local out_changed = false + for v, _ in pairs(input) do if not kill[v] then - if not A[v] then - changed = true + if not output[v] then + out_changed = true end - A[v] = true + output[v] = true + end + end + + for v, _ in pairs(output) do + if not input[v] then + output[v] = nil + out_changed = true end end - return changed + return out_changed + end + + local function merge_uninit(input, output) + for v, _ in pairs(output) do + input[v] = true + end + end + + local function empty_set(S) + for v,_ in pairs(S) do + S[v] = nil + end end local succ_list = ir.get_successor_list(block_list) + local pred_list = ir.get_predecessor_list(block_list) local block_order = ir.get_successor_depth_search_topological_sort(succ_list) - local function block_analysis(block_i) + local dirty_flag = {} -- { block_id -> bool? } keeps track of modified blocks + for i = 1, #block_list do + dirty_flag[i] = true + end + + local function update_block(block_i) local block_succs = succ_list[block_i] - local uninit = uninit_sets[block_i] - local kill = kill_sets[block_i] - local changed = false - for _,succ in ipairs(block_succs) do - local c = merge_uninit(uninit_sets[succ], uninit, kill) - changed = c or changed + local block_preds = pred_list[block_i] + local state = state_list[block_i] + + -- first block's input is supposed to be fixed + if block_i ~= 1 then + empty_set(state.input) + for _,pred in ipairs(block_preds) do + local pred_out = state_list[pred].output + merge_uninit(state.input, pred_out) + end + end + + local out_changed = apply_kill_set(state) + if out_changed then + for _, succ in ipairs(block_succs) do + dirty_flag[succ] = true + end end - return changed end repeat - local changed = false + local found_dirty_block = false for _,block_i in ipairs(block_order) do - changed = block_analysis(block_i) or changed + if dirty_flag[block_i] then + found_dirty_block = true + -- CAREFUL: we have to clean the dirty flag BEFORE updating the block or else we + -- will do the wrong thing for auto-referencing blocks + dirty_flag[block_i] = nil + update_block(block_i) + end end - until not changed + until not found_dirty_block end local function gen_kill_set(block) @@ -91,36 +148,31 @@ function uninitialized.verify_variables(module) local nvars = #func.vars local nargs = #func.typ.arg_types - -- initialize sets - - -- variables that are initialized inside a given block - local kill_sets = {} -- { block_id -> {var_id -> bool?} } - - -- variables that are uninitialized when entering a given block - local uninit_sets = {} -- { block_id -> {var_id -> bool?} } - for _,b in ipairs(func.blocks) do - local kill = gen_kill_set(b) - table.insert(kill_sets, kill) - table.insert(uninit_sets, {}) + local state_list = {} -- { FlowState } + -- initialize states + for block_i,block in ipairs(func.blocks) do + local fst = FlowState() + fst.kill = gen_kill_set(block) + state_list[block_i] = fst end - local entry_uninit = uninit_sets[1] + local entry_input = state_list[1].input for v_i = nargs+1, nvars do - entry_uninit[v_i] = true + entry_input[v_i] = true end -- solve flow equations - flow_analysis(func.blocks, uninit_sets, kill_sets) + flow_analysis(func.blocks, state_list) -- check for errors local reported_variables = {} -- (only one error message per variable) for block_i, block in ipairs(func.blocks) do - local input_uninit = uninit_sets[block_i] + local uninit = copy_set(state_list[block_i].input) for _, cmd in ipairs(block.cmds) do local loc = cmd.loc - fill_set(cmd, input_uninit, nil) + fill_set(cmd, uninit, nil) for _, src in ipairs(ir.get_srcs(cmd)) do local v = src.id - if src._tag == "ir.Value.LocalVar" and input_uninit[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) @@ -132,7 +184,7 @@ function uninitialized.verify_variables(module) end end - local exit_uninit = uninit_sets[#func.blocks] + local exit_uninit = state_list[#func.blocks].output if #func.ret_vars > 0 then local ret1 = func.ret_vars[1] if exit_uninit[ret1] then From ef32c2d685dd679ab095947c3f19a8e2bceb0f6d Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Mon, 22 Jul 2024 15:35:24 -0300 Subject: [PATCH 4/5] PR fixes --- src/pallene/gc.lua | 8 ++++---- src/pallene/uninitialized.lua | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/pallene/gc.lua b/src/pallene/gc.lua index 75efcb5d..99744548 100644 --- a/src/pallene/gc.lua +++ b/src/pallene/gc.lua @@ -79,7 +79,7 @@ local function flow_analysis(block_list, state_list) end for v, g in pairs(gen) do - assert(g ~= true or g ~= kill[v], "gen and kill can't both be true") + assert(not (g and kill[v]), "gen and kill can't both be true") local previous_val = input[v] local new_val = true input[v] = new_val @@ -102,7 +102,7 @@ local function flow_analysis(block_list, state_list) end end - local function empty_set(S) + local function clear_set(S) for v,_ in pairs(S) do S[v] = nil end @@ -124,7 +124,7 @@ local function flow_analysis(block_list, state_list) -- last block's output is supposed to be fixed if block_i ~= #block_list then - empty_set(state.output) + clear_set(state.output) for _,succ in ipairs(block_succs) do local succ_in = state_list[succ].input merge_live(succ_in, state.output) @@ -146,7 +146,7 @@ local function flow_analysis(block_list, state_list) found_dirty_block = true -- CAREFUL: we have to clean the dirty flag BEFORE updating the block or else we -- will do the wrong thing for auto-referencing blocks - dirty_flag[block_i] = nil + dirty_flag[block_i] = false update_block(block_i) end end diff --git a/src/pallene/uninitialized.lua b/src/pallene/uninitialized.lua index d16cf3da..48a9c33d 100644 --- a/src/pallene/uninitialized.lua +++ b/src/pallene/uninitialized.lua @@ -80,7 +80,7 @@ local function flow_analysis(block_list, state_list) end end - local function empty_set(S) + local function clear_set(S) for v,_ in pairs(S) do S[v] = nil end @@ -102,7 +102,7 @@ local function flow_analysis(block_list, state_list) -- first block's input is supposed to be fixed if block_i ~= 1 then - empty_set(state.input) + clear_set(state.input) for _,pred in ipairs(block_preds) do local pred_out = state_list[pred].output merge_uninit(state.input, pred_out) @@ -124,7 +124,7 @@ local function flow_analysis(block_list, state_list) found_dirty_block = true -- CAREFUL: we have to clean the dirty flag BEFORE updating the block or else we -- will do the wrong thing for auto-referencing blocks - dirty_flag[block_i] = nil + dirty_flag[block_i] = false update_block(block_i) end end From dca2093ead0fc7333b21af7c15626f63454a6525 Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Tue, 23 Jul 2024 16:55:10 -0300 Subject: [PATCH 5/5] PR fixes --- src/pallene/coder.lua | 413 +++++++++++++++++----------------- src/pallene/gc.lua | 22 +- src/pallene/uninitialized.lua | 6 +- 3 files changed, 226 insertions(+), 215 deletions(-) diff --git a/src/pallene/coder.lua b/src/pallene/coder.lua index 70d26ce1..0730f1b9 100644 --- a/src/pallene/coder.lua +++ b/src/pallene/coder.lua @@ -984,9 +984,11 @@ end -- the stack top before a GC point so the GC can look at the right set of variables. We also need -- to do it before function calls because the stack-gowing logic relies on having the right "top". -local function update_stack_top(gc_info) +function Coder:update_stack_top(cmd_position) + local gc_info = self.gc[self.current_func] + local live_vars = gc_info.live_gc_vars[cmd_position.block_index][cmd_position.cmd_index] local offset = 0 - for _, v_id in ipairs(gc_info.live_gc_vars) do + for _, v_id in ipairs(live_vars) do local slot = gc_info.slot_of_variable[v_id] offset = math.max(offset, slot + 1) end @@ -1007,15 +1009,15 @@ end local gen_cmd = {} -gen_cmd["Move"] = function(self, st) - local dst = self:c_var(st.cmd.dst) - local src = self:c_value(st.cmd.src) +gen_cmd["Move"] = function(self, args) + local dst = self:c_var(args.cmd.dst) + local src = self:c_value(args.cmd.src) return (util.render([[ $dst = $src; ]], { dst = dst, src = src })) end -gen_cmd["Unop"] = function(self, st) - local dst = self:c_var(st.cmd.dst) - local x = self:c_value(st.cmd.src) +gen_cmd["Unop"] = function(self, args) + local dst = self:c_var(args.cmd.dst) + local x = self:c_value(args.cmd.src) -- For when we can directly translate to a C operator: local function unop(op) @@ -1033,8 +1035,8 @@ gen_cmd["Unop"] = function(self, st) ${check_no_metatable} $dst = luaH_getn($x); ]], { - check_no_metatable = check_no_metatable(self, x, st.cmd.loc), - line = C.integer(st.cmd.loc.line), + check_no_metatable = check_no_metatable(self, x, args.cmd.loc), + line = C.integer(args.cmd.loc.line), dst = dst, x = x })) @@ -1045,7 +1047,7 @@ gen_cmd["Unop"] = function(self, st) dst = dst, x = x })) end - local op = st.cmd.op + local op = args.cmd.op if op == "ArrLen" then return arr_len() elseif op == "StrLen" then return str_len() elseif op == "IntNeg" then return int_neg() @@ -1057,10 +1059,10 @@ gen_cmd["Unop"] = function(self, st) end end -gen_cmd["Binop"] = function(self, st) - local dst = self:c_var(st.cmd.dst) - local x = self:c_value(st.cmd.src1) - local y = self:c_value(st.cmd.src2) +gen_cmd["Binop"] = function(self, args) + local dst = self:c_var(args.cmd.dst) + local x = self:c_value(args.cmd.src1) + local y = self:c_value(args.cmd.src2) -- For when we can be directly translate to a C operator: local function binop(op) @@ -1082,7 +1084,7 @@ gen_cmd["Binop"] = function(self, st) -- For integer division and modulus: local function int_division(fname) - local line = st.cmd.loc.line + local line = args.cmd.loc.line return (util.render([[ $dst = $fname(L, $x, $y, PALLENE_SOURCE_FILE, $line); ]], { fname = fname, dst = dst, @@ -1124,7 +1126,7 @@ gen_cmd["Binop"] = function(self, st) dst = dst, x = x, y = y, op = op })) end - local op = st.cmd.op + local op = args.cmd.op if op == "IntAdd" then return int_binop("+") elseif op == "IntSub" then return int_binop("-") elseif op == "IntMul" then return int_binop("*") @@ -1180,11 +1182,11 @@ gen_cmd["Binop"] = function(self, st) end end -gen_cmd["Concat"] = function(self, st) - local dst = self:c_var(st.cmd.dst) +gen_cmd["Concat"] = function(self, args) + local dst = self:c_var(args.cmd.dst) local init_input_array = {} - for ix, srcv in ipairs(st.cmd.srcs) do + for ix, srcv in ipairs(args.cmd.srcs) do local src = self:c_value(srcv) table.insert(init_input_array, util.render([[ ss[$i] = $src; ]], { @@ -1201,60 +1203,60 @@ gen_cmd["Concat"] = function(self, st) } ]], { dst = dst, - N = C.integer(#st.cmd.srcs), + N = C.integer(#args.cmd.srcs), init_input_array = table.concat(init_input_array, "\n"), })) end -gen_cmd["ToFloat"] = function(self, st) - local dst = self:c_var(st.cmd.dst) - local v = self:c_value(st.cmd.src) +gen_cmd["ToFloat"] = function(self, args) + local dst = self:c_var(args.cmd.dst) + local v = self:c_value(args.cmd.src) return util.render([[ $dst = (lua_Number) $v; ]], { dst = dst, v = v }) end -gen_cmd["ToDyn"] = function(self, st) - local dst = self:c_var(st.cmd.dst) - local src = self:c_value(st.cmd.src) - local src_typ = st.cmd.src_typ +gen_cmd["ToDyn"] = function(self, args) + local dst = self:c_var(args.cmd.dst) + local src = self:c_value(args.cmd.src) + local src_typ = args.cmd.src_typ return (set_stack_slot(src_typ, "&"..dst, src)) end -gen_cmd["FromDyn"] = function(self, st) - local dst = self:c_var(st.cmd.dst) - local src = self:c_value(st.cmd.src) - local dst_typ = st.cmd.dst_typ +gen_cmd["FromDyn"] = function(self, args) + local dst = self:c_var(args.cmd.dst) + local src = self:c_value(args.cmd.src) + local dst_typ = args.cmd.dst_typ return self:get_stack_slot(dst_typ, dst, "&"..src, - st.cmd.loc, "downcasted value") + args.cmd.loc, "downcasted value") end -gen_cmd["IsTruthy"] = function(self, st) - local dst = self:c_var(st.cmd.dst) - local src = self:c_value(st.cmd.src) +gen_cmd["IsTruthy"] = function(self, args) + local dst = self:c_var(args.cmd.dst) + local src = self:c_value(args.cmd.src) return (util.render([[ $dst = pallene_is_truthy(&$src); ]], { dst = dst, src = src })) end -gen_cmd["IsNil"] = function(self, st) - local dst = self:c_var(st.cmd.dst) - local src = self:c_value(st.cmd.src) +gen_cmd["IsNil"] = function(self, args) + local dst = self:c_var(args.cmd.dst) + local src = self:c_value(args.cmd.src) return (util.render([[ $dst = ttisnil(&$src); ]], { dst = dst, src = src })) end -gen_cmd["NewArr"] = function(self, st) - local dst = self:c_var(st.cmd.dst) - local n = self:c_value(st.cmd.src_size) +gen_cmd["NewArr"] = function(self, args) + local dst = self:c_var(args.cmd.dst) + local n = self:c_value(args.cmd.src_size) return (util.render([[ $dst = pallene_createtable(L, $n, 0); ]], { dst = dst, n = n, })) end -gen_cmd["GetArr"] = function(self, st) - local dst = self:c_var(st.cmd.dst) - local arr = self:c_value(st.cmd.src_arr) - local i = self:c_value(st.cmd.src_i) - local dst_typ = st.cmd.dst_typ - local line = C.integer(st.cmd.loc.line) +gen_cmd["GetArr"] = function(self, args) + local dst = self:c_var(args.cmd.dst) + local arr = self:c_value(args.cmd.src_arr) + local i = self:c_value(args.cmd.src_i) + local dst_typ = args.cmd.dst_typ + local line = C.integer(args.cmd.loc.line) return (util.render([[ { @@ -1267,16 +1269,16 @@ gen_cmd["GetArr"] = function(self, st) i = i, line = line, get_slot = self:get_luatable_slot(dst_typ, dst, "slot", arr, - st.cmd.loc, "array element"), + args.cmd.loc, "array element"), })) end -gen_cmd["SetArr"] = function(self, st) - local arr = self:c_value(st.cmd.src_arr) - local i = self:c_value(st.cmd.src_i) - local v = self:c_value(st.cmd.src_v) - local src_typ = st.cmd.src_typ - local line = C.integer(st.cmd.loc.line) +gen_cmd["SetArr"] = function(self, args) + local arr = self:c_value(args.cmd.src_arr) + local i = self:c_value(args.cmd.src_i) + local v = self:c_value(args.cmd.src_v) + local src_typ = args.cmd.src_typ + local line = C.integer(args.cmd.loc.line) return (util.render([[ { pallene_renormalize_array(L, $arr, $i, PALLENE_SOURCE_FILE, $line); @@ -1292,23 +1294,23 @@ gen_cmd["SetArr"] = function(self, st) })) end -gen_cmd["NewTable"] = function(self, st) - local dst = self:c_var(st.cmd.dst) - local n = self:c_value(st.cmd.src_size) +gen_cmd["NewTable"] = function(self, args) + local dst = self:c_var(args.cmd.dst) + local n = self:c_value(args.cmd.src_size) return (util.render([[ $dst = pallene_createtable(L, 0, $n); ]], { dst = dst, n = n, })) end -gen_cmd["GetTable"] = function(self, st) - local dst = self:c_var(st.cmd.dst) - local tab = self:c_value(st.cmd.src_tab) - local key = self:c_value(st.cmd.src_k) - local dst_typ = st.cmd.dst_typ +gen_cmd["GetTable"] = function(self, args) + local dst = self:c_var(args.cmd.dst) + local tab = self:c_value(args.cmd.src_tab) + local key = self:c_value(args.cmd.src_k) + local dst_typ = args.cmd.dst_typ - assert(st.cmd.src_k._tag == "ir.Value.String") - local field_name = st.cmd.src_k.value + assert(args.cmd.src_k._tag == "ir.Value.String") + local field_name = args.cmd.src_k.value return util.render([[ { @@ -1320,18 +1322,18 @@ gen_cmd["GetTable"] = function(self, st) field_len = tostring(#field_name), tab = tab, key = key, - get_slot = self:get_luatable_slot(dst_typ, dst, "slot", tab, st.cmd.loc, "table field"), + get_slot = self:get_luatable_slot(dst_typ, dst, "slot", tab, args.cmd.loc, "table field"), }) end -gen_cmd["SetTable"] = function(self, st) - local tab = self:c_value(st.cmd.src_tab) - local key = self:c_value(st.cmd.src_k) - local val = self:c_value(st.cmd.src_v) - local src_typ = st.cmd.src_typ +gen_cmd["SetTable"] = function(self, args) + local tab = self:c_value(args.cmd.src_tab) + local key = self:c_value(args.cmd.src_k) + local val = self:c_value(args.cmd.src_v) + local src_typ = args.cmd.src_typ - assert(st.cmd.src_k._tag == "ir.Value.String") - local field_name = st.cmd.src_k.value + assert(args.cmd.src_k._tag == "ir.Value.String") + local field_name = args.cmd.src_k.value return util.render([[ { @@ -1360,22 +1362,22 @@ gen_cmd["SetTable"] = function(self, st) }) end -gen_cmd["NewRecord"] = function(self, st) - local rc = self.record_coders[st.cmd.rec_typ] - local rec = self:c_var(st.cmd.dst) +gen_cmd["NewRecord"] = function(self, args) + local rc = self.record_coders[args.cmd.rec_typ] + local rec = self:c_var(args.cmd.dst) return (util.render([[$rec = $constructor(L, K);]] , { rec = rec, constructor = rc:constructor_name(), })) end -gen_cmd["GetField"] = function(self, st) - local rec_typ = st.cmd.rec_typ +gen_cmd["GetField"] = function(self, args) + local rec_typ = args.cmd.rec_typ local rc = self.record_coders[rec_typ] - local dst = self:c_var(st.cmd.dst) - local rec = self:c_value(st.cmd.src_rec) - local field_name = st.cmd.field_name + local dst = self:c_var(args.cmd.dst) + local rec = self:c_value(args.cmd.src_rec) + local field_name = args.cmd.field_name local f_typ = rec_typ.field_types[field_name] if types.is_gc(f_typ) then @@ -1391,13 +1393,13 @@ gen_cmd["GetField"] = function(self, st) end end -gen_cmd["SetField"] = function(self, st) - local rec_typ = st.cmd.rec_typ +gen_cmd["SetField"] = function(self, args) + local rec_typ = args.cmd.rec_typ local rc = self.record_coders[rec_typ] - local rec = self:c_value(st.cmd.src_rec) - local v = self:c_value(st.cmd.src_v) - local field_name = st.cmd.field_name + local rec = self:c_value(args.cmd.src_rec) + local v = self:c_value(args.cmd.src_v) + local field_name = args.cmd.field_name local f_typ = rec_typ.field_types[field_name] if types.is_gc(f_typ) then @@ -1409,8 +1411,8 @@ gen_cmd["SetField"] = function(self, st) end end -gen_cmd["NewClosure"] = function (self, st) - local func = self.module.functions[st.cmd.f_id] +gen_cmd["NewClosure"] = function (self, args) + local func = self.module.functions[args.cmd.f_id] -- The number of upvalues must fit inside a byte (the nupvalues in the ClosureHeader). -- However, we must check this limit ourselves, because luaF_newCclosure doesn't. If we have too @@ -1427,19 +1429,19 @@ gen_cmd["NewClosure"] = function (self, st) } ]], { num_upvalues = C.integer(num_upvalues), - dst = self:c_var(st.cmd.dst), - lua_entry_point = self:lua_entry_point_name(st.cmd.f_id), + dst = self:c_var(args.cmd.dst), + lua_entry_point = self:lua_entry_point_name(args.cmd.f_id), }) end -gen_cmd["InitUpvalues"] = function(self, st) - local func = self.module.functions[st.cmd.f_id] +gen_cmd["InitUpvalues"] = function(self, args) + local func = self.module.functions[args.cmd.f_id] - assert(st.cmd.src_f._tag == "ir.Value.LocalVar") - local cclosure = string.format("clCvalue(&%s)", self:c_var(st.cmd.src_f.id)) + assert(args.cmd.src_f._tag == "ir.Value.LocalVar") + local cclosure = string.format("clCvalue(&%s)", self:c_var(args.cmd.src_f.id)) local capture_upvalues = {} - for i, val in ipairs(st.cmd.srcs) do + for i, val in ipairs(args.cmd.srcs) do local typ = func.captured_vars[i].typ local c_val = self:c_value(val) local upvalue_dst = string.format("&(ccl->upvalue[%s])", C.integer(i)) @@ -1463,35 +1465,35 @@ gen_cmd["InitUpvalues"] = function(self, st) }) end -gen_cmd["CallStatic"] = function(self, st) +gen_cmd["CallStatic"] = function(self, args) local dsts = {} - for i, dst in ipairs(st.cmd.dsts) do + for i, dst in ipairs(args.cmd.dsts) do dsts[i] = dst and self:c_var(dst) end local xs = {} - for _, x in ipairs(st.cmd.srcs) do + for _, x in ipairs(args.cmd.srcs) do table.insert(xs, self:c_value(x)) end local parts = {} - local f_val = st.cmd.src_f + local f_val = args.cmd.src_f local f_id, cclosure if f_val._tag == "ir.Value.Upvalue" then - f_id = assert(st.func.f_id_of_upvalue[f_val.id]) + f_id = assert(args.func.f_id_of_upvalue[f_val.id]) cclosure = string.format("clCvalue(&%s)", self:c_value(f_val)) elseif f_val._tag == "ir.Value.LocalVar" then - f_id = assert(st.func.f_id_of_local[f_val.id]) + f_id = assert(args.func.f_id_of_local[f_val.id]) cclosure = string.format("clCvalue(&%s)", self:c_value(f_val)) else tagged_union.error(f_val._tag) end - table.insert(parts, update_stack_top(st.gc_info)) + table.insert(parts, self:update_stack_top(args.position)) if self.flags.use_traceback then table.insert(parts, string.format("PALLENE_SETLINE(%d);\n", - st.func.loc and st.func.loc.line or 0)) + args.func.loc and args.func.loc.line or 0)) end table.insert(parts, self:call_pallene_function(dsts, f_id, cclosure, xs, nil)) @@ -1499,24 +1501,25 @@ gen_cmd["CallStatic"] = function(self, st) return table.concat(parts, "\n") end -gen_cmd["CallDyn"] = function(self, st) - local f_typ = st.cmd.f_typ +gen_cmd["CallDyn"] = function(self, args) + local f_typ = args.cmd.f_typ local dsts = {} - for i, dst in ipairs(st.cmd.dsts) do + for i, dst in ipairs(args.cmd.dsts) do dsts[i] = dst and self:c_var(dst) end local push_arguments = {} - table.insert(push_arguments, self:push_to_stack(f_typ, self:c_value(st.cmd.src_f))) + table.insert(push_arguments, self:push_to_stack(f_typ, self:c_value(args.cmd.src_f))) for i = 1, #f_typ.arg_types do local typ = f_typ.arg_types[i] - table.insert(push_arguments, self:push_to_stack(typ, self:c_value(st.cmd.srcs[i]))) + table.insert(push_arguments, self:push_to_stack(typ, self:c_value(args.cmd.srcs[i]))) end local pop_results = {} for i = #f_typ.ret_types, 1, -1 do local typ = f_typ.ret_types[i] - local get_slot = self:get_stack_slot(typ, dsts[i], "slot", st.cmd.loc, "return value #%d", i) + local get_slot = + self:get_stack_slot(typ, dsts[i], "slot", args.cmd.loc, "return value #%d", i) table.insert(pop_results, util.render([[ { L->top.p--; @@ -1531,7 +1534,7 @@ gen_cmd["CallDyn"] = function(self, st) local setline = "" if self.flags.use_traceback then setline = util.render([[ PALLENE_SETLINE($line); ]], { - line = C.integer(st.func.loc and st.func.loc.line or 0) + line = C.integer(args.func.loc and args.func.loc.line or 0) }) end @@ -1543,7 +1546,7 @@ gen_cmd["CallDyn"] = function(self, st) ${pop_results} ${restore_stack} ]], { - update_stack_top = update_stack_top(st.gc_info), + update_stack_top = self:update_stack_top(args.position), push_arguments = table.concat(push_arguments, "\n"), setline = setline, pop_results = table.concat(pop_results, "\n"), @@ -1553,43 +1556,43 @@ gen_cmd["CallDyn"] = function(self, st) }) end -gen_cmd["BuiltinIoWrite"] = function(self, st) - local v = self:c_value(st.cmd.srcs[1]) +gen_cmd["BuiltinIoWrite"] = function(self, args) + local v = self:c_value(args.cmd.srcs[1]) return util.render([[ pallene_io_write(L, $v); ]], { v = v }) end -gen_cmd["BuiltinMathAbs"] = function(self, st) - local dst = self:c_var(st.cmd.dsts[1]) - local v = self:c_value(st.cmd.srcs[1]) +gen_cmd["BuiltinMathAbs"] = function(self, args) + local dst = self:c_var(args.cmd.dsts[1]) + local v = self:c_value(args.cmd.srcs[1]) return util.render([[ $dst = l_mathop(fabs)($v); ]], { dst = dst, v = v }) end -gen_cmd["BuiltinMathCeil"] = function(self, st) - local dst = self:c_var(st.cmd.dsts[1]) - local v = self:c_value(st.cmd.srcs[1]) - local line = st.cmd.loc.line +gen_cmd["BuiltinMathCeil"] = function(self, args) + local dst = self:c_var(args.cmd.dsts[1]) + local v = self:c_value(args.cmd.srcs[1]) + local line = args.cmd.loc.line return util.render([[ $dst = pallene_math_ceil(L, PALLENE_SOURCE_FILE, $line, $v); ]], { dst = dst, v = v, line = C.integer(line) }) end -gen_cmd["BuiltinMathFloor"] = function(self, st) - local dst = self:c_var(st.cmd.dsts[1]) - local v = self:c_value(st.cmd.srcs[1]) - local line = st.cmd.loc.line +gen_cmd["BuiltinMathFloor"] = function(self, args) + local dst = self:c_var(args.cmd.dsts[1]) + local v = self:c_value(args.cmd.srcs[1]) + local line = args.cmd.loc.line return util.render([[ $dst = pallene_math_floor(L, PALLENE_SOURCE_FILE, $line, $v); ]], { dst = dst, v = v, line = C.integer(line) }) end -gen_cmd["BuiltinMathFmod"] = function(self, st) - local dst = self:c_var(st.cmd.dsts[1]) - local x = self:c_value(st.cmd.srcs[1]) - local y = self:c_value(st.cmd.srcs[2]) +gen_cmd["BuiltinMathFmod"] = function(self, args) + local dst = self:c_var(args.cmd.dsts[1]) + local x = self:c_value(args.cmd.srcs[1]) + local y = self:c_value(args.cmd.srcs[2]) return util.render([[ $dst = l_mathop(fmod)($x, $y); ]], { dst = dst, x = x, y = y }) end -gen_cmd["BuiltinMathExp"] = function(self, st) - local dst = self:c_var(st.cmd.dsts[1]) - local v = self:c_value(st.cmd.srcs[1]) +gen_cmd["BuiltinMathExp"] = function(self, args) + local dst = self:c_var(args.cmd.dsts[1]) + local v = self:c_value(args.cmd.srcs[1]) return util.render([[ $dst = l_mathop(exp)($v); ]], { dst = dst, v = v }) end @@ -1599,69 +1602,69 @@ end -- But for --emit-lua, we must do something to make the code work in pure Lua. -- For now, the easiest thing to do is inject math.ln = math.log at the top. -- A smarter routine would replace math.ln with math.log. -gen_cmd["BuiltinMathLn"] = function(self, st) - local dst = self:c_var(st.cmd.dsts[1]) - local v = self:c_value(st.cmd.srcs[1]) +gen_cmd["BuiltinMathLn"] = function(self, args) + local dst = self:c_var(args.cmd.dsts[1]) + local v = self:c_value(args.cmd.srcs[1]) return util.render([[ $dst = l_mathop(log)($v); ]], { dst = dst, v = v }) end -gen_cmd["BuiltinMathLog"] = function(self, st) - local dst = self:c_var(st.cmd.dsts[1]) - local v = self:c_value(st.cmd.srcs[1]) - local b = self:c_value(st.cmd.srcs[2]) +gen_cmd["BuiltinMathLog"] = function(self, args) + local dst = self:c_var(args.cmd.dsts[1]) + local v = self:c_value(args.cmd.srcs[1]) + local b = self:c_value(args.cmd.srcs[2]) return util.render([[ $dst = pallene_math_log($v, $b); ]], { dst = dst, v = v, b = b }) end -gen_cmd["BuiltinMathModf"] = function(self, st) - local dst1 = self:c_var(st.cmd.dsts[1]) - local dst2 = self:c_var(st.cmd.dsts[2]) - local v = self:c_value(st.cmd.srcs[1]) - local line = st.cmd.loc.line +gen_cmd["BuiltinMathModf"] = function(self, args) + local dst1 = self:c_var(args.cmd.dsts[1]) + local dst2 = self:c_var(args.cmd.dsts[2]) + local v = self:c_value(args.cmd.srcs[1]) + local line = args.cmd.loc.line return util.render([[ $dst1 = pallene_math_modf(L, PALLENE_SOURCE_FILE, $line, $v, &$dst2); ]], { dst1 = dst1, dst2 = dst2, v = v, line = C.integer(line) }) end -gen_cmd["BuiltinMathPow"] = function(self, st) - local dst = self:c_var(st.cmd.dsts[1]) - local x = self:c_value(st.cmd.srcs[1]) - local y = self:c_value(st.cmd.srcs[2]) +gen_cmd["BuiltinMathPow"] = function(self, args) + local dst = self:c_var(args.cmd.dsts[1]) + local x = self:c_value(args.cmd.srcs[1]) + local y = self:c_value(args.cmd.srcs[2]) return util.render([[ $dst = l_mathop(pow)($x, $y); ]], { dst = dst, x = x, y = y }) end -gen_cmd["BuiltinMathSqrt"] = function(self, st) - local dst = self:c_var(st.cmd.dsts[1]) - local v = self:c_value(st.cmd.srcs[1]) +gen_cmd["BuiltinMathSqrt"] = function(self, args) + local dst = self:c_var(args.cmd.dsts[1]) + local v = self:c_value(args.cmd.srcs[1]) return util.render([[ $dst = l_mathop(sqrt)($v); ]], { dst = dst, v = v }) end -gen_cmd["BuiltinStringChar"] = function(self, st) - local dst = self:c_var(st.cmd.dsts[1]) - local v = self:c_value(st.cmd.srcs[1]) - local line = st.cmd.loc.line +gen_cmd["BuiltinStringChar"] = function(self, args) + local dst = self:c_var(args.cmd.dsts[1]) + local v = self:c_value(args.cmd.srcs[1]) + local line = args.cmd.loc.line return util.render([[ $dst = pallene_string_char(L, PALLENE_SOURCE_FILE, $line, $v); ]], { dst = dst, v = v, line = C.integer(line) }) end -gen_cmd["BuiltinStringSub"] = function(self, st) - local dst = self:c_var(st.cmd.dsts[1]) - local str = self:c_value(st.cmd.srcs[1]) - local i = self:c_value(st.cmd.srcs[2]) - local j = self:c_value(st.cmd.srcs[3]) +gen_cmd["BuiltinStringSub"] = function(self, args) + local dst = self:c_var(args.cmd.dsts[1]) + local str = self:c_value(args.cmd.srcs[1]) + local i = self:c_value(args.cmd.srcs[2]) + local j = self:c_value(args.cmd.srcs[3]) return util.render([[ $dst = pallene_string_sub(L, $str, $i, $j); ]], { dst = dst, str = str, i = i, j = j }) end -gen_cmd["BuiltinType"] = function(self, st) - local dst = self:c_var(st.cmd.dsts[1]) - local v = self:c_value(st.cmd.srcs[1]) +gen_cmd["BuiltinType"] = function(self, args) + local dst = self:c_var(args.cmd.dsts[1]) + local v = self:c_value(args.cmd.srcs[1]) return util.render([[ $dst = pallene_type_builtin(L, $v); ]], { dst = dst, v = v }) end -gen_cmd["BuiltinTostring"] = function(self, st) - local dst = self:c_var(st.cmd.dsts[1]) - local v = self:c_value(st.cmd.srcs[1]) - local line = st.cmd.loc.line +gen_cmd["BuiltinTostring"] = function(self, args) + local dst = self:c_var(args.cmd.dsts[1]) + local v = self:c_value(args.cmd.srcs[1]) + local line = args.cmd.loc.line return util.render([[ $dst = pallene_tostring(L, PALLENE_SOURCE_FILE, $line, $v); ]], { dst = dst, line = C.integer(line), v = v }) end @@ -1670,8 +1673,8 @@ end -- Control flow -- -gen_cmd["ForPrep"] = function(self, st) - local typ = st.func.vars[st.cmd.dst_i].typ +gen_cmd["ForPrep"] = function(self, args) + local typ = args.func.vars[args.cmd.dst_i].typ local macro if typ._tag == "types.T.Integer" then macro = "PALLENE_INT_FOR_PREP" @@ -1685,18 +1688,18 @@ gen_cmd["ForPrep"] = function(self, st) ${macro}($i, $cond, $iter, $count, $start, $limit, $step) ]], { macro = macro, - i = self:c_var(st.cmd.dst_i), - cond = self:c_var(st.cmd.dst_cond), - iter = self:c_var(st.cmd.dst_iter), - count = self:c_var(st.cmd.dst_count), - start = self:c_value(st.cmd.src_start), - limit = self:c_value(st.cmd.src_limit), - step = self:c_value(st.cmd.src_step), + i = self:c_var(args.cmd.dst_i), + cond = self:c_var(args.cmd.dst_cond), + iter = self:c_var(args.cmd.dst_iter), + count = self:c_var(args.cmd.dst_count), + start = self:c_value(args.cmd.src_start), + limit = self:c_value(args.cmd.src_limit), + step = self:c_value(args.cmd.src_step), })) end -gen_cmd["ForStep"] = function(self, st) - local typ = st.func.vars[st.cmd.dst_i].typ +gen_cmd["ForStep"] = function(self, args) + local typ = args.func.vars[args.cmd.dst_i].typ local macro if typ._tag == "types.T.Integer" then @@ -1711,31 +1714,31 @@ gen_cmd["ForStep"] = function(self, st) ${macro}($i, $cond, $iter, $count, $start, $limit, $step) ]], { macro = macro, - i = self:c_var(st.cmd.dst_i), - cond = self:c_var(st.cmd.dst_cond), - iter = self:c_var(st.cmd.dst_iter), - count = self:c_var(st.cmd.dst_count), - start = self:c_value(st.cmd.src_start), - limit = self:c_value(st.cmd.src_limit), - step = self:c_value(st.cmd.src_step), + i = self:c_var(args.cmd.dst_i), + cond = self:c_var(args.cmd.dst_cond), + iter = self:c_var(args.cmd.dst_iter), + count = self:c_var(args.cmd.dst_count), + start = self:c_value(args.cmd.src_start), + limit = self:c_value(args.cmd.src_limit), + step = self:c_value(args.cmd.src_step), })) end -gen_cmd["Jmp"] = function(self, st) - return "goto " .. self:c_label(st.cmd.target) .. ";" +gen_cmd["Jmp"] = function(self, args) + return "goto " .. self:c_label(args.cmd.target) .. ";" end -gen_cmd["JmpIf"] = function(self, st) +gen_cmd["JmpIf"] = function(self, args) return util.render("if($v) {goto $t;} else {goto $f;}", { - v = self:c_value(st.cmd.src_cond), - t = self:c_label(st.cmd.target_true), - f = self:c_label(st.cmd.target_false), + v = self:c_value(args.cmd.src_cond), + t = self:c_label(args.cmd.target_true), + f = self:c_label(args.cmd.target_false), }) end -gen_cmd["CheckGC"] = function(self, st) +gen_cmd["CheckGC"] = function(self, args) return util.render([[ luaC_condGC(L, ${update_stack_top}, (void)0); ]], { - update_stack_top = update_stack_top(st.gc_info) }) + update_stack_top = self:update_stack_top(args.position) }) end function Coder:generate_blocks(func) @@ -1746,18 +1749,15 @@ function Coder:generate_blocks(func) })) for cmd_i,cmd in ipairs(block.cmds) do if cmd._tag ~= "ir.Cmd.Jmp" or cmd.target ~= block_i + 1 then - local gen_state = { + local gen_args = { cmd = cmd, func = func, - gc_info = false, + position = { + block_index = block_i, + cmd_index = cmd_i, + }, } - if gc.cmd_uses_gc(cmd._tag) then - gen_state.gc_info = { - live_gc_vars = self.gc[func].live_gc_vars[block_i][cmd_i], - slot_of_variable = self.gc[func].slot_of_variable, - } - end - local cmd_str = self:generate_cmd(gen_state) .. "\n" + local cmd_str = self:generate_cmd(gen_args) .. "\n" table.insert(out, cmd_str) end end @@ -1765,16 +1765,17 @@ function Coder:generate_blocks(func) return table.concat(out) end -function Coder:generate_cmd(gen_state) - local cmd = gen_state.cmd - local func = gen_state.func +function Coder:generate_cmd(gen_args) + local cmd = gen_args.cmd + local func = gen_args.func assert(tagged_union.typename(cmd._tag) == "ir.Cmd") local name = tagged_union.consname(cmd._tag) local f = assert(gen_cmd[name], "impossible") - local out = f(self, gen_state) + local out = f(self, gen_args) + local slot_of_variable = self.gc[func].slot_of_variable for _, v_id in ipairs(ir.get_dsts(cmd)) do - local n = self.gc[func].slot_of_variable[v_id] + local n = slot_of_variable[v_id] if n then local typ = func.vars[v_id].typ local slot = util.render([[s2v(base + $n)]], { n = C.integer(n) }) diff --git a/src/pallene/gc.lua b/src/pallene/gc.lua index 99744548..35b6a60d 100644 --- a/src/pallene/gc.lua +++ b/src/pallene/gc.lua @@ -37,14 +37,14 @@ local gc = {} local function FlowState() return { - input = {}, -- {var_id -> bool?} live variables at block start - output = {}, -- {var_id -> bool?} live variables at block end - kill = {}, -- {var_id -> bool?} variables that are killed inside block - gen = {}, -- {var_id -> bool?} variables that become live inside block + input = {}, -- set of var_id, live variables at block start + output = {}, -- set of var_id, live variables at block end + kill = {}, -- set of var_id, variables that are killed inside block + gen = {}, -- set of var_id, variables that become live inside block } end -function gc.cmd_uses_gc(tag) +local function cmd_uses_gc(tag) assert(tagged_union.typename(tag) == "ir.Cmd") return tag == "ir.Cmd.CallStatic" or tag == "ir.Cmd.CallDyn" or @@ -175,6 +175,16 @@ local function make_gen_kill_sets(block, flow_state) end end +-- Returns information that is used for allocating variables into the Lua stack. +-- The returned data is: +-- * live_gc_vars: +-- for each command, has a list of GC'd variables that are alive during that command. +-- * live_at_same_time: +-- for each GC'd variable, indicates what other GC'd variables are alive at the same time, +-- that is, if both are alive during the same command for some command in the function. +-- * max_frame_size: +-- what's the maximum number of slots of the Lua stack used for storing GC'd variables +-- during the function. function gc.compute_stack_slots(func) local state_list = {} -- { FlowState } @@ -235,7 +245,7 @@ function gc.compute_stack_slots(func) end end - if gc.cmd_uses_gc(cmd._tag) + if cmd_uses_gc(cmd._tag) then local lives_cmd = {} for var,_ in pairs(lives_block) do diff --git a/src/pallene/uninitialized.lua b/src/pallene/uninitialized.lua index 48a9c33d..7ede0165 100644 --- a/src/pallene/uninitialized.lua +++ b/src/pallene/uninitialized.lua @@ -15,9 +15,9 @@ local uninitialized = {} local function FlowState() return { - input = {}, -- {var_id -> bool?} uninitialized variables at block start - output = {}, -- {var_id -> bool?} uninitialized variables at block end - kill = {}, -- {var_id -> bool?} variables that are initialized inside block + input = {}, -- set of var_id, uninitialized variables at block start + output = {}, -- set of var_id, uninitialized variables at block end + kill = {}, -- set of var_id, variables that are initialized inside block } end