switcher: add scripts/dot_from_switcher.lua
As embarrassing as it might be, it's still better to share than not.
diff --git a/scripts/dot_from_switcher.lua b/scripts/dot_from_switcher.lua
new file mode 100644
index 0000000..387ec66
--- /dev/null
+++ b/scripts/dot_from_switcher.lua
@@ -0,0 +1,414 @@
+#!/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("}")