| #!/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("}") |