| #!/usr/bin/env lua5.3 |
| -- Copyright CHERIoT Contributors. |
| -- SPDX-License-Identifier: MIT |
| |
| local infile = io.input() |
| |
| local labels = {} -- label |-> style array |
| local label_outs = {} -- label |-> label |-> style |
| local label_ins = {} -- label |-> label |-> () |
| |
| local label_irq_assume = {} -- label |-> { "deferred", "enabled" } |
| local label_irq_require = {} -- label |-> { "deferred", "enabled" } |
| |
| local exports = {} -- label |-> () |
| |
| local lastlabels = {} |
| |
| local function debug() end |
| if true then |
| function debug(...) |
| io.stderr:write(string.format("DBG %s\n", |
| table.concat(table.pack(...), " "))) |
| end |
| end |
| |
| local function end_label(clear) |
| -- At the end of a lable, if we haven't been told that we've assumed a |
| -- different IRQ disposition than required on the way in, then inherit |
| -- assumptions from requirements |
| local lastlabel = lastlabels[#lastlabels] |
| if label_irq_assume[lastlabel] == nil then |
| label_irq_assume[lastlabel] = |
| assert(label_irq_require[lastlabel], |
| "Missing IRQ requirement for prior label, cannot inherit assumption") |
| end |
| |
| if clear then lastlabels = {} end |
| end |
| |
| local function found_label(label, where) |
| assert(label) |
| assert(where) |
| |
| assert(labels[label] == nil, label) |
| labels[label] = { ("tooltip=%s"):format(where) } |
| |
| if label_ins[label] == nil then |
| label_ins[label] = {} |
| end |
| |
| if label_outs[label] == nil then |
| label_outs[label] = {} |
| end |
| |
| if #lastlabels > 0 then end_label(false) end |
| |
| table.insert(lastlabels, label) |
| if #lastlabels > 2 then table.remove(lastlabels, 1) end |
| assert(#lastlabels <= 2) |
| end |
| |
| local function found_edge_from(from, to, style) |
| assert(from) |
| assert(to) |
| |
| debug("", "EDGE FROM", from, to) |
| |
| if label_outs[from] == nil then label_outs[from] = {} end |
| label_outs[from][to] = {style} |
| end |
| |
| local function found_edge_to(from, to) |
| assert(from) |
| assert(to) |
| |
| debug("", "EDGE TO", from, to) |
| |
| if label_ins[to] == nil then label_ins[to] = {} end |
| label_ins[to][from] = true |
| end |
| |
| local lineix = 1 |
| |
| -- Read and discard until we get to the good stuff |
| for line in infile:lines("*l") do |
| debug("HUNT", line) |
| if line:match(".globl __Z26compartment_switcher_entryz") then break end |
| lineix = lineix + 1 |
| end |
| |
| local IRQ_dispositions = |
| { ["any"] = true |
| , ["deferred"] = true |
| , ["enabled"] = true |
| } |
| |
| -- And here we go |
| for line in infile:lines("*l") do |
| local label |
| |
| debug("LINE", line) |
| |
| -- numeric labels are suppresed |
| label = line:match("^(%d+):$") |
| if label then |
| debug("", "Numeric label") |
| goto nextline |
| end |
| |
| -- local labels |
| label = line:match("^(%.L.*):$") |
| if label then |
| debug("", "Local label") |
| found_label(label, lineix) |
| if #lastlabels > 1 then |
| found_edge_to(lastlabels[#lastlabels-1], lastlabels[#lastlabels]) |
| end |
| goto nextline |
| end |
| |
| -- documentation-only labels |
| label = line:match("^//(%.L.*):$") |
| if label then |
| debug("", "Documentation label", #lastlabels) |
| found_label(label, lineix) |
| |
| -- Documentation labels are presumed to be fall-through and do not need the |
| -- clutter of "FROM: above" |
| assert(#lastlabels > 1) |
| found_edge_from(lastlabels[#lastlabels-1], lastlabels[#lastlabels]) |
| found_edge_to(lastlabels[#lastlabels-1], lastlabels[#lastlabels]) |
| |
| -- Documentation labels are presumed to inherit the IRQ disposition from |
| -- "above" as well. |
| label_irq_require[lastlabels[#lastlabels]] = |
| assert(label_irq_assume[lastlabels[#lastlabels-1]], |
| "Missing IRQ disposition for prior label") |
| |
| goto nextline |
| end |
| |
| -- other global labels |
| label = line:match("^([%w_]*):$") |
| if label then |
| debug("", "global label") |
| found_label(label, lineix) |
| if #lastlabels > 1 then |
| found_edge_to(lastlabels[#lastlabels-1], lastlabels[#lastlabels]) |
| end |
| exports[label] = true |
| goto nextline |
| end |
| |
| -- [cm]ret clear the last label, preventing fallthru |
| if line:match("^%s+[cm]ret$") then |
| debug("", "[cm]ret") |
| end_label(true) |
| goto nextline |
| end |
| |
| -- unconditonal jumps add an edge and clear the last label, since we cannot |
| -- be coming "FROM: above" |
| label = line:match("^%s+j%s+(%g*)$") |
| if label then |
| debug("", "Jump") |
| assert(#lastlabels > 0) |
| found_edge_to(lastlabels[#lastlabels], label) |
| end_label(true) |
| goto nextline |
| end |
| |
| -- branches add edges to local labels |
| label = line:match("^%s+b.*,%s*(%.L%g*)$") |
| if label then |
| debug("", "Branch") |
| assert(#lastlabels > 0) |
| found_edge_to(lastlabels[#lastlabels], label) |
| goto nextline |
| end |
| |
| -- OK, now hunt for structured comments. |
| line = line:match("^%s*%*%s*(%S.*)$") or line:match("^%s*//%s*(%S.*)$") |
| if not line then goto nextline end |
| |
| -- "FROM: malice" annotations promote lastlabel to being exported |
| label = line:match("^FROM:%s+malice%s*") |
| if label then |
| debug("", "Malice", #lastlabels) |
| assert(#lastlabels > 0) |
| exports[lastlabels[#lastlabels]] = true |
| goto nextline |
| end |
| |
| -- "FROM: above" |
| label = line:match("^FROM:%s+above%s*") |
| if label then |
| debug("", "Above") |
| assert(#lastlabels > 1) |
| found_edge_from(lastlabels[#lastlabels-1], lastlabels[#lastlabels]) |
| |
| -- "FROM: above" implies IRQ requirements, too |
| label_irq_require[lastlabels[#lastlabels]] = |
| assert(label_irq_assume[lastlabels[#lastlabels-1]], |
| "Missing IRQ disposition for prior label") |
| |
| goto nextline |
| end |
| |
| -- "IFROM: above" |
| label = line:match("^IFROM:%s+above%s*") |
| if label then |
| debug("", "Above") |
| assert(#lastlabels > 1) |
| found_edge_from(lastlabels[#lastlabels-1], |
| lastlabels[#lastlabels], |
| "style=dashed") |
| |
| -- "IFROM: above" implies IRQ requirements, too |
| label_irq_require[lastlabels[#lastlabels]] = |
| assert(label_irq_assume[lastlabels[#lastlabels-1]], |
| "Missing IRQ disposition for prior label") |
| |
| goto nextline |
| end |
| |
| -- "FROM: cross-call" no-op |
| label = line:match("^FROM:%s+cross%-call%s*") |
| if label then |
| goto nextline |
| end |
| |
| -- "FROM: interrupt" no-op |
| label = line:match("^FROM:%s+interrupt%s*") |
| if label then |
| goto nextline |
| end |
| |
| -- "FROM: error" no-op |
| label = line:match("^FROM:%s+interrupt%s*") |
| if label then |
| goto nextline |
| end |
| |
| -- "FROM: $symbol" |
| label = line:match("^FROM:%s+(%S+)%s*") |
| if label then |
| debug("", "FROM", lastlabels[#lastlabels], label) |
| assert(#lastlabels > 0) |
| found_edge_from(label, lastlabels[#lastlabels]) |
| goto nextline |
| end |
| |
| -- "IFROM: $symbol" |
| label = line:match("^IFROM:%s+(%S+)%s*") |
| if label then |
| debug("", "IFROM", lastlabels[#lastlabels], label) |
| assert(#lastlabels > 0) |
| found_edge_from(label, lastlabels[#lastlabels], "style=dashed") |
| goto nextline |
| end |
| |
| -- "IFROM: $symbol" |
| label = line:match("^ITO:%s+(%S+)%s*") |
| if label then |
| debug("", "ITO", lastlabels[#lastlabels], label) |
| assert(#lastlabels > 0) |
| found_edge_to(lastlabels[#lastlabels], label, "style=dashed") |
| goto nextline |
| end |
| |
| -- "IRQ ASSUME: {deferred,enabled}" |
| label = line:match("^IRQ ASSUME:%s+(%S+)%s*") |
| if label then |
| debug("", "IRQ ASSUME", lastlabels[#lastlabels], label) |
| assert (IRQ_dispositions[label]) |
| label_irq_assume[lastlabels[#lastlabels]] = label |
| goto nextline |
| end |
| |
| -- "IRQ REQURE: {deferred,enabled}" |
| label = line:match("^IRQ REQUIRE:%s+(%S+)%s*") |
| if label then |
| debug("", "IRQ", lastlabels[#lastlabels], label) |
| assert (IRQ_dispositions[label]) |
| label_irq_require[lastlabels[#lastlabels]] = label |
| goto nextline |
| end |
| |
| -- Stop reading when we get to the uninteresting library exports |
| if line:match("Switcher%-exported library functions%.$") then |
| debug("", "Break") |
| break |
| end |
| |
| ::nextline:: |
| lineix = lineix + 1 |
| end |
| |
| -- Take adjacency matrix representation and add lists. |
| label_inls = {} |
| label_outls = {} |
| for focus, _ in pairs(labels) do |
| label_inls[focus] = {} |
| label_outls[focus] = {} |
| |
| for from, _ in pairs(label_ins[focus]) do |
| assert(labels[from]) |
| assert(label_outs[from][focus], |
| string.format("%s in from %s but no out edge", focus, from)) |
| assert( label_irq_require[focus] == "any" |
| or label_irq_assume[from] == label_irq_require[focus], |
| string.format("IRQ-invalid arc from %s (%s) to %s (%s)", |
| from, label_irq_assume[from], focus, label_irq_require[focus])) |
| |
| table.insert(label_inls[focus], from) |
| end |
| for to, _ in pairs(label_outs[focus]) do |
| assert(labels[to]) |
| assert(label_ins[to][focus], |
| string.format("%s out to %s but no in edge", focus, to)) |
| assert( label_irq_require[to] == "any" |
| or label_irq_assume[focus] == label_irq_require[to], |
| string.format("IRQ-invalid arc from %s (%s) to %s (%s)", |
| focus, label_irq_assume[focus], to, label_irq_require[to])) |
| |
| table.insert(label_outls[focus], to) |
| end |
| end |
| |
| local function render_exports(...) |
| local args = {...} |
| |
| local nexports = 0 |
| for export, _ in pairs(exports) do nexports = nexports + 1 end |
| assert(nexports == #args, |
| ("Wrong number of exports: %d != %d"):format(nexports, #args)) |
| |
| print(" { rank=min; edge [style=invis]; ") |
| |
| for _, export in ipairs(args) do |
| assert(exports[export], "Purported export isn't") |
| print("", ("%q"):format(export), ";") |
| end |
| |
| for i = 1, #args-1 do |
| print("", ("%q -> %q ;"):format(args[i], args[i+1])) |
| end |
| |
| print(" }") |
| end |
| |
| print("digraph switcher {") |
| |
| -- Put all our exports at the top of the graph, in a fixed order. |
| render_exports(".Lhandle_error_handler_return", |
| "exception_entry_asm", |
| "switcher_after_compartment_call", |
| "__Z26compartment_switcher_entryz") |
| |
| for from, from_params in pairs(labels) do |
| |
| if exports[from] then |
| table.insert(from_params, "shape=box") |
| elseif #label_inls[from] == 1 then |
| -- Indegree 1, this is either an exit, a decision node, or just a waypoint |
| if #label_outls[from] == 0 then |
| -- Exit |
| table.insert(from_params, "shape=octagon") |
| elseif #label_outls[from] == 1 then |
| -- Waypoint |
| table.insert(from_params, "shape=oval") |
| else |
| -- Decision |
| table.insert(from_params, "shape=trapezium") |
| end |
| else |
| if #label_outls[from] == 0 then |
| -- Exit |
| table.insert(from_params, "shape=octagon") |
| elseif #label_outls[from] == 1 then |
| table.insert(from_params, "shape=invtrapezium") |
| else |
| table.insert(from_params, "shape=hexagon") |
| end |
| end |
| |
| table.insert(from_params, |
| ({ ["any"] = "fontname=\"Times\"" |
| , ["deferred"] = "fontname=\"Times-Bold\"" |
| , ["enabled"] = "fontname=\"Times-Italic\"" |
| })[label_irq_assume[from]]) |
| |
| if from:match("^%.?L?switch") |
| or from == "__Z26compartment_switcher_entryz" then |
| table.insert(from_params, "style=filled") |
| table.insert(from_params, "fillcolor=cyan") |
| elseif from:match("^%.?L?exception") then |
| table.insert(from_params, "style=filled") |
| table.insert(from_params, "fillcolor=red") |
| elseif from:match("^%.?L?handle") then |
| table.insert(from_params, "style=filled") |
| table.insert(from_params, "fillcolor=orange") |
| end |
| |
| print("", ("%q [%s];"):format(from, table.concat(from_params,","))) |
| |
| for to, style in pairs(label_outs[from]) do |
| print("", ("%q"):format(from), |
| ("-> %q [%s];"):format(to, table.concat(style,","))) |
| end |
| print("") |
| end |
| |
| print("}") |