wyre/compiler/verilog_generator.mu
2021-09-26 02:03:13 +02:00

929 lines
21 KiB
Text

GeneratorState struct #RefType {
comp Compilation
symbols Map<string, Node>
typeMap Map<Node, Tag>
constMap Map<Node, Value>
entities List<Node>
errors List<Error>
es EmulatorState
inst ModuleInstance
evalCtxOutput int
evalCtxField int
isNonIndexable bool
out CodeBuilder
first CodeBuilder
assignments CodeBuilder
regUpdates CodeBuilder
globals Set<string>
}
CodeBuilder struct #RefType {
sb StringBuilder
indent int
lineSep string
skipSep bool
create() {
return CodeBuilder { sb: new StringBuilder{} }
}
}
NodeWithCtx struct {
node Node
ctxOutput int
ctxField int
hash(self NodeWithCtx) {
return xor(xor(transmute(pointer_cast(self.node, pointer), uint), cast(self.ctxOutput, uint) << 25), cast(self.ctxField, uint))
}
equals(a NodeWithCtx, b NodeWithCtx) {
return a.node == b.node && a.ctxOutput == b.ctxOutput && a.ctxField == b.ctxField
}
}
VerilogGenerator {
comp(comp Compilation, es EmulatorState) {
s := new GeneratorState {
comp: comp,
symbols: comp.symbols,
typeMap: comp.typeMap,
constMap: comp.constMap,
entities: comp.entities,
errors: comp.errors,
es: es,
evalCtxOutput: -1,
evalCtxField: -1,
out: new CodeBuilder.create(),
globals: new Set.create<string>(),
}
s.globals.add("module")
s.globals.add("endmodule")
s.globals.add("begin")
s.globals.add("end")
s.globals.add("if")
s.globals.add("else")
s.globals.add("posedge")
s.globals.add("negedge")
s.globals.add("wire")
s.globals.add("reg")
s.globals.add("input")
s.globals.add("output")
s.globals.add("inout")
s.globals.add("case")
s.globals.add("signed")
s.globals.add("cell")
for u in s.comp.units {
for node in u.contents {
if node.is(ModuleDef) {
def := node.as(ModuleDef)
if def.blackboxKeyword != null {
s.globals.tryAdd(def.name.value)
}
}
}
}
for mi in es.moduleInstances {
if mi.def.blackboxKeyword == null {
mi.genLocals = new Map.create<NodeWithCtx, string>()
mi.genLocalsSet = new Set.create<string>()
name := mi.fullName != "" ? mi.fullName.replace(".", "_") : mi.def.name.value
mi.genGlobalName = uniqueName(s, mi, s.globals, name)
} else {
mi.genGlobalName = mi.def.name.value
}
}
for mi in es.moduleInstances {
s.globals.tryRemove(mi.genGlobalName)
}
write(s, format("// Generated by Wyre compiler {}\n", compilerVersion))
for mi in es.moduleInstances {
if mi.def.blackboxKeyword == null {
module(s, mi)
}
}
return s.out
}
module(s GeneratorState, inst ModuleInstance) {
s.inst = inst
def := inst.def
for i := 1; i < inst.calls.count {
mi := s.es.moduleInstances[inst.calls[i]]
mi.genLocalName = uniqueName(s, s.inst, s.inst.genLocalsSet, mi.localName)
}
write(s, "\nmodule ")
write(s, inst.genGlobalName)
write(s, "(")
indent(s, ",")
for inp in def.inputs {
if inp.flags & ModuleInputFlags.static == 0 {
tag := s.typeMap.get(inp)
if tag.kind == TagKind.number {
input(s, inp, nameOf(s, s.inst, inp, -1, -1), tag)
} else if tag.kind == TagKind.struct_ {
sdef := s.entities[tag.q].as(StructDef)
for f, fi in sdef.fields {
input(s, inp, nameOf(s, s.inst, inp, -1, fi), s.typeMap.get(f))
}
} else {
abandon()
}
}
}
for o in def.outputs {
tag := s.typeMap.get(o)
if tag.kind == TagKind.number {
output(s, o, nameOf(s, s.inst, o, -1, -1), tag)
} else if tag.kind == TagKind.struct_ {
sdef := s.entities[tag.q].as(StructDef)
for f, fi in sdef.fields {
s.evalCtxField = fi
output(s, o, nameOf(s, s.inst, o, -1, fi), s.typeMap.get(f))
}
s.evalCtxField = -1
} else {
abandon()
}
}
unindent(s)
beginLine(s)
write(s, ");\n")
s.assignments = new CodeBuilder.create()
s.regUpdates = new CodeBuilder.create()
s.first = push(s, s.regUpdates)
firstPos := s.first.sb.count
block(s, def.body)
if s.regUpdates.sb.count > 0 {
beginLine(s)
}
for i := 1; i < inst.calls.count {
callInst(s, s.es.moduleInstances[inst.calls[i]])
}
restore(s, s.first)
if s.first.sb.count != firstPos {
beginLine(s)
}
str := s.assignments.sb.compactToString()
if str != "" {
write(s, str)
beginLine(s)
}
str = s.regUpdates.sb.compactToString()
if str != "" {
write(s, str)
}
if inst.calls.count == 0 {
beginLine(s)
}
write(s, "\nendmodule\n")
}
input(s GeneratorState, inp ModuleInputDef, genName string, tag Tag) {
beginLine(s)
write(s, "input ")
numberTag(s, tag)
write(s, genName)
}
output(s GeneratorState, o AssignStatement, genName string, tag Tag) {
isReg := o.regKeyword != null
beginLine(s)
write(s, "output ")
if isReg {
write(s, "reg ")
}
numberTag(s, tag)
write(s, genName)
if isReg {
if o.expr != null && o.flags & AssignFlags.regUpdate == 0 {
write(s, " = ")
staticValue(s, s.inst, o.localId, tag)
} else {
write(s, " = 0")
}
}
}
callInst(s GeneratorState, target ModuleInstance) {
def := target.def
callExpr := target.callExpr
beginLine(s)
write(s, "(* keep *) ")
write(s, target.genGlobalName)
write(s, " ")
if def.blackboxKeyword != null {
if hasStaticInputs(s, def) {
write(s, "#(")
indent(s, ",")
for inp in def.inputs {
if inp.flags & ModuleInputFlags.static != 0 {
tag := s.typeMap.get(inp)
beginLine(s)
write(s, ".")
write(s, trimHash(inp.name.value))
write(s, "(")
staticValue(s, target, inp.localId, tag)
write(s, ")")
}
}
unindent(s)
beginLine(s)
write(s, ") ")
}
}
write(s, target.genLocalName)
write(s, "(")
indent(s, ",")
for inp in def.inputs {
if inp.flags & ModuleInputFlags.static == 0 {
tag := s.typeMap.get(inp)
if tag.kind == TagKind.number {
beginLine(s)
write(s, ".")
write(s, def.blackboxKeyword == null ? nameOf(s, target, inp, -1, -1) : inp.name.value)
write(s, "(")
expression(s, callExpr.args[callExpr.calleeLocalIdToArgIndex[inp.localId]].expr)
write(s, ")")
} else if tag.kind == TagKind.struct_ {
sdef := unpackStruct(s, tag)
for f, i in sdef.fields {
beginLine(s)
write(s, ".")
write(s, def.blackboxKeyword == null ? nameOf(s, target, inp, -1, i) : inp.name.value)
write(s, "(")
s.evalCtxField = i
expression(s, callExpr.args[callExpr.calleeLocalIdToArgIndex[inp.localId]].expr)
write(s, ")")
}
s.evalCtxField = -1
} else {
abandon()
}
}
}
for o, oi in def.outputs {
tag := s.typeMap.get(o)
if tag.kind == TagKind.number {
beginLine(s)
write(s, ".")
write(s, def.blackboxKeyword == null ? nameOf(s, target, o, -1, -1) : o.nameExpr.as(Token).value)
write(s, "(")
write(s, nameOf(s, s.inst, callExpr, oi, -1))
write(s, ")")
} else if tag.kind == TagKind.struct_ {
sdef := unpackStruct(s, tag)
for f, fi in sdef.fields {
beginLine(s)
write(s, ".")
write(s, def.blackboxKeyword == null ? nameOf(s, target, o, -1, fi) : o.nameExpr.as(Token).value)
write(s, "(")
write(s, nameOf(s, s.inst, callExpr, oi, fi))
write(s, ")")
}
} else {
abandon()
}
}
unindent(s)
beginLine(s)
write(s, ");")
prev := push(s, s.first)
for o, oi in def.outputs {
tag := s.typeMap.get(o)
if tag.kind == TagKind.number {
beginLine(s)
write(s, "wire ")
numberTag(s, tag)
write(s, nameOf(s, s.inst, callExpr, oi, -1))
write(s, ";")
} else if tag.kind == TagKind.struct_ {
sdef := unpackStruct(s, tag)
for f, fi in sdef.fields {
beginLine(s)
write(s, "wire ")
numberTag(s, s.typeMap.get(f))
write(s, nameOf(s, s.inst, callExpr, oi, fi))
write(s, ";")
}
} else {
abandon()
}
}
restore(s, prev)
beginLine(s)
}
block(s GeneratorState, block Block) {
for n in block.contents {
match n {
ClockStatement: clock(s, n)
IfStatement: if_(s, n)
AssignStatement: assign(s, n)
}
}
}
clock(s GeneratorState, st ClockStatement) {
beginLine(s)
write(s, "always @(")
write(s, st.keyword.value)
write(s, " ")
write(s, st.name.value)
write(s, ") begin")
indent(s, "")
block(s, st.body)
unindent(s)
beginLine(s)
write(s, "end")
}
if_(s GeneratorState, st IfStatement) {
beginLine(s)
write(s, "if (")
expression(s, st.expr)
write(s, ") begin")
indent(s, "")
block(s, st.ifBody)
unindent(s)
if st.elseBranch != null {
beginLine(s)
write(s, "end else begin")
indent(s, "")
if st.elseBranch.is(IfStatement) {
if_(s, st.elseBranch.as(IfStatement))
} else if st.elseBranch.is(Block) {
block(s, st.elseBranch.as(Block))
}
unindent(s)
}
beginLine(s)
write(s, "end")
}
assign(s GeneratorState, st AssignStatement) {
if (st.flags & AssignFlags.reg) != 0 && st.outKeyword == null {
prev := push(s, s.first)
name := st.nameExpr.as(Token).value
tag := s.typeMap.get(st)
if tag.kind == TagKind.number {
reg(s, st, nameOf(s, s.inst, st, -1, -1), tag)
} else if tag.kind == TagKind.struct_ {
def := s.entities[tag.q].as(StructDef)
for f, fi in def.fields {
s.evalCtxField = fi
reg(s, st, nameOf(s, s.inst, st, -1, fi), s.typeMap.get(f))
}
s.evalCtxField = -1
} else {
abandon()
}
restore(s, prev)
}
if (st.flags & AssignFlags.wire) != 0 && (st.flags & AssignFlags.static) == 0 {
prev := push(s, s.first)
tag := s.typeMap.get(st)
if tag.kind == TagKind.number {
wire(s, st, nameOf(s, s.inst, st, -1, -1), tag)
} else if tag.kind == TagKind.struct_ {
def := s.entities[tag.q].as(StructDef)
for f, fi in def.fields {
s.evalCtxField = fi
wire(s, st, nameOf(s, s.inst, st, -1, fi), s.typeMap.get(f))
}
s.evalCtxField = -1
} else if tag.kind == TagKind.moduleOut {
// OK
} else {
abandon()
}
restore(s, prev)
}
if st.flags & AssignFlags.regUpdate != 0 {
nameExpr := st.nameExpr
match nameExpr {
Token: {
sym := s.inst.def.symbols.get(nameExpr.value)
tag := s.typeMap.get(sym)
if tag.kind == TagKind.number {
regUpdate(s, nameOf(s, s.inst, sym, -1, -1), st.expr)
} else if tag.kind == TagKind.struct_ {
def := s.entities[tag.q].as(StructDef)
for f, fi in def.fields {
s.evalCtxField = fi
regUpdate(s, nameOf(s, s.inst, sym, -1, fi), st.expr)
}
s.evalCtxField = -1
} else {
abandon()
}
}
DotExpression: {
dot := nameExpr
nameToken := dot.lhs.as(Token)
sym := s.inst.def.symbols.getOrDefault(nameToken.value)
tag := s.typeMap.get(sym)
sdef := unpackStruct(s, tag)
fieldIndex := sdef.symbols.get(dot.rhs.value).fieldIndex
regUpdate(s, nameOf(s, s.inst, sym, -1, fieldIndex), st.expr)
}
}
}
}
reg(s GeneratorState, st AssignStatement, genName string, tag Tag) {
beginLine(s)
write(s, "(* keep *) reg ")
numberTag(s, tag)
write(s, genName)
if st.expr != null && st.flags & AssignFlags.regUpdate == 0 {
write(s, " = ")
staticValue(s, s.inst, st.localId, tag)
} else {
write(s, " = 0")
}
write(s, ";")
}
wire(s GeneratorState, st AssignStatement, genName string, tag Tag) {
if st.outKeyword == null {
beginLine(s)
write(s, "wire ")
numberTag(s, tag)
write(s, genName)
write(s, ";")
}
cb := new CodeBuilder.create()
prev := push(s, cb)
beginLine(s)
write(s, "assign ")
write(s, genName)
write(s, " = ")
expression(s, st.expr)
write(s, ";")
s.out = s.assignments
write(s, cb.sb.compactToString())
restore(s, prev)
}
regUpdate(s GeneratorState, genName string, expr Node) {
beginLine(s)
write(s, genName)
write(s, " <= ")
expression(s, expr)
write(s, ";")
}
expression(s GeneratorState, e Node) {
val := s.constMap.getOrDefault(e)
if val.kind != ValueKind.none {
assert(s.evalCtxField == -1 && s.evalCtxOutput == -1)
tag := s.typeMap.get(e)
value(s, tag, val)
} else {
expressionInner(s, e)
}
}
expressionInner(s GeneratorState, e Node) {
match e {
Token: token(s, e)
NumberExpression: number(s, e)
UnaryOperatorExpression: unaryOperator(s, e)
BinaryOperatorExpression: binaryOperator(s, e)
DotExpression: dot(s, e)
TernaryOperatorExpression: ternaryOperator(s, e)
MatchExpression: match_(s, e)
ParenExpression: paren(s, e)
IndexExpression: index(s, e)
CallExpression: call(s, e)
StructInitializerExpression: structInit(s, e)
BraceExpression: brace(s, e)
}
}
token(s GeneratorState, e Token) {
name := e.value
sym := s.inst.def.symbols.getOrDefault(name)
if s.evalCtxOutput == -1 {
match sym {
ModuleInputDef: {
if sym.flags & ModuleInputFlags.static == 0 {
write(s, nameOf(s, s.inst, sym, -1, s.evalCtxField))
} else {
staticValue(s, s.inst, sym.localId, s.typeMap.get(sym))
}
}
AssignStatement: {
if sym.flags & AssignFlags.static == 0 {
write(s, nameOf(s, s.inst, sym, -1, s.evalCtxField))
} else {
staticValue(s, s.inst, sym.localId, s.typeMap.get(sym))
}
}
}
} else {
assign := sym.as(AssignStatement)
assert(assign.expr.is(CallExpression))
expression(s, assign.expr)
}
}
number(s GeneratorState, e NumberExpression) {
s.isNonIndexable = true
sb := s.out.sb
assert(s.evalCtxField == -1 && s.evalCtxOutput == -1)
assert(e.dontCare != 0)
tag := s.typeMap.get(e)
assert(tag.kind == TagKind.number)
assert(tag.q > 0)
mask := 1_uL << (tag.q - 1)
tag.q.writeTo(sb)
sb.write("'b")
while mask != 0 {
if mask & e.dontCare != 0 {
sb.write("x")
} else if mask & e.value != 0 {
sb.write("1")
} else {
sb.write("0")
}
mask >>= 1
}
}
unaryOperator(s GeneratorState, e UnaryOperatorExpression) {
if e.op.value == "zx" {
expression(s, e.expr)
return
}
s.isNonIndexable = true
write(s, e.op.value)
write(s, "(")
expression(s, e.expr)
write(s, ")")
}
binaryOperator(s GeneratorState, e BinaryOperatorExpression) {
s.isNonIndexable = true
write(s, "(")
expression(s, e.lhs)
write(s, ") ")
write(s, e.op.value)
write(s, " (")
expression(s, e.rhs)
write(s, ")")
}
dot(s GeneratorState, e DotExpression) {
tag := s.typeMap.get(e.lhs)
if tag.kind == TagKind.struct_ {
assert(s.evalCtxField == -1)
def := s.entities[tag.q].as(StructDef)
s.evalCtxField = def.symbols.get(e.rhs.value).fieldIndex
expression(s, e.lhs)
s.evalCtxField = -1
} else if tag.kind == TagKind.moduleOut {
assert(s.evalCtxOutput == -1)
def := s.entities[tag.q].as(ModuleDef)
s.evalCtxOutput = def.symbols.get(e.rhs.value).as(AssignStatement).outputIndex
expression(s, e.lhs)
s.evalCtxOutput = -1
} else {
abandon()
}
}
ternaryOperator(s GeneratorState, e TernaryOperatorExpression) {
s.isNonIndexable = true
write(s, "(")
expression(s, e.conditionExpr)
write(s, ") ? (")
expression(s, e.trueExpr)
write(s, ") : (")
expression(s, e.falseExpr)
write(s, ")")
}
match_(s GeneratorState, e MatchExpression) {
eb := new CodeBuilder.create()
prev := push(s, eb)
s.isNonIndexable = false
expression(s, e.target)
restore(s, prev)
target := ""
if !s.isNonIndexable {
target = eb.sb.compactToString()
} else {
target = newLocal(s, s.typeMap.get(e.target), eb.sb.compactToString())
}
indent(s, "")
for cs in e.cases {
beginLine(s)
write(s, "(((")
write(s, target)
write(s, ") == ")
expression(s, cs.valueExpr)
write(s, ") ? (")
expression(s, cs.resultExpr)
write(s, ") :")
}
write(s, " ")
resultTag := s.typeMap.get(e)
write(s, format("{}'b{}", resultTag.q, string.repeatChar('x', resultTag.q)))
write(s, string.repeatChar(')', e.cases.count))
unindent(s)
s.isNonIndexable = true
}
paren(s GeneratorState, e ParenExpression) {
s.isNonIndexable = true
write(s, "(")
expression(s, e.expr)
write(s, ")")
}
index(s GeneratorState, e IndexExpression) {
eb := new CodeBuilder.create()
prev := push(s, eb)
s.isNonIndexable = false
expression(s, e.target)
restore(s, prev)
if !s.isNonIndexable {
write(s, eb.sb.compactToString())
} else {
write(s, newLocal(s, s.typeMap.get(e.target), eb.sb.compactToString()))
}
sb := s.out.sb
sb.write("[")
upper := s.constMap.get(e.upperExpr).z
upper.writeTo(sb)
if e.lowerExpr != null {
sb.write(":")
lower := s.constMap.get(e.lowerExpr).z
lower.writeTo(sb)
}
sb.write("]")
s.isNonIndexable = true
}
call(s GeneratorState, e CallExpression) {
if e.builtin == BuiltinCall.rep {
rep(s, e)
return
}
assert(e.builtin == BuiltinCall.none)
assert(s.evalCtxOutput >= 0)
write(s, nameOf(s, s.inst, e, s.evalCtxOutput, s.evalCtxField))
}
rep(s GeneratorState, e CallExpression) {
s.isNonIndexable = true
n := TypeChecker.unpackInt(s.constMap.get(e.args[1].expr))
write(s, "{ ")
sep := false
for i := 0; i < n {
if sep {
write(s, ", ")
} else {
sep = true
}
expression(s, e.args[0].expr)
}
write(s, " }")
}
structInit(s GeneratorState, e StructInitializerExpression) {
assert(s.evalCtxField >= 0)
def := unpackStruct(s, s.typeMap.get(e))
argIndex := e.fieldIndexToArgIndex[s.evalCtxField]
if argIndex >= 0 {
arg := e.args[argIndex].expr
prevCtxField := s.evalCtxField
s.evalCtxField = -1
expression(s, arg)
s.evalCtxField = prevCtxField
} else {
s.isNonIndexable = true
write(s, "0")
}
}
brace(s GeneratorState, e BraceExpression) {
s.isNonIndexable = true
write(s, "{ ")
sep := false
for arg in e.args {
if sep {
write(s, ", ")
} else {
sep = true
}
expression(s, arg)
}
write(s, " }")
}
newLocal(s GeneratorState, tag Tag, valStr string) {
prev := push(s, s.first)
beginLine(s)
write(s, "wire ")
numberTag(s, tag)
name := uniqueName(s, s.inst, s.inst.genLocalsSet, "local")
write(s, name)
write(s, ";")
restore(s, prev)
prev = push(s, s.assignments)
beginLine(s)
write(s, "assign ")
write(s, name)
write(s, " = ")
write(s, valStr)
write(s, ";")
restore(s, prev)
return name
}
staticValue(s GeneratorState, instance ModuleInstance, localId int, tag Tag) {
si := instance.localState[localId] + max(0, s.evalCtxField)
value(s, tag, s.es.rs[si])
}
value(s GeneratorState, tag Tag, value Value) {
s.isNonIndexable = true
assert(tag.kind == TagKind.number)
if tag.q == 0 {
write(s, "(((compiler_bug:invalid_num")
}
if value.kind == ValueKind.ulong_ {
write(s, format("{}'d{}", tag.q, value.z))
} else if value.kind == ValueKind.byteArray {
data := EmulatorRunner.unpackArray(value)
write(s, format("{}'h", tag.q))
from := min((tag.q + 7) / 8, data.count)
if from > 0 {
for i := from - 1; i >= 0; i -= 1 {
Util.writeByteHexTo(data[i], s.out.sb)
}
} else {
write(s, "0")
}
} else {
abandon()
}
}
numberTag(s GeneratorState, tag Tag) {
assert(tag.kind == TagKind.number)
assert(tag.q > 0)
if tag.q > 1 {
write(s, format("[{}:0] ", tag.q - 1))
}
}
nameOf(s GeneratorState, inst ModuleInstance, node Node, ctxOutput int, ctxField int) string {
nc := NodeWithCtx { node: node, ctxOutput: ctxOutput, ctxField: ctxField }
existingName := inst.genLocals.maybeGet(nc)
if existingName.hasValue {
return existingName.value
}
sb := StringBuilder{}
match node {
ModuleInputDef: {
sb.write(trimHash(node.name.value))
if ctxField != -1 {
sb.write("_")
sb.write(unpackStruct(s, s.typeMap.get(node)).fields[ctxField].name.value)
}
}
AssignStatement: {
assert(s.typeMap.get(node).kind != TagKind.moduleOut)
sb.write(trimHash(node.nameExpr.as(Token).value))
if ctxField != -1 {
sb.write("_")
sb.write(unpackStruct(s, s.typeMap.get(node)).fields[ctxField].name.value)
}
}
CallExpression: {
target := s.es.moduleInstances[inst.calls[node.callId]]
output := target.def.outputs[ctxOutput]
sb.write(target.genLocalName)
sb.write("_")
sb.write(target.def.blackboxKeyword == null ? nameOf(s, target, output, -1, ctxField) : output.nameExpr.as(Token).value)
}
}
name := uniqueName(s, inst, inst.genLocalsSet, sb.compactToString())
inst.genLocals.add(nc, name)
return name
}
uniqueName(s GeneratorState, inst ModuleInstance, set Set<string>, origName string) {
name := origName
i := 1
while true {
if !s.globals.contains(name) && !inst.genLocalsSet.contains(name) {
set.add(name)
return name
}
name = format("{}_{}", origName, i)
i += 1
}
}
hasStaticInputs(s GeneratorState, def ModuleDef) {
result := false
for inp in def.inputs {
result ||= (inp.flags & ModuleInputFlags.static) != 0
}
return result
}
trimHash(s string) {
if s[0] == '#' {
return s.slice(1, s.length)
}
return s
}
beginLine(s GeneratorState) {
if !s.out.skipSep {
write(s, s.out.lineSep)
} else {
s.out.skipSep = false
}
write(s, "\n")
for i := 0; i < s.out.indent {
s.out.sb.writeChar('\t')
}
}
write(s GeneratorState, str string) {
s.out.sb.write(str)
}
indent(s GeneratorState, lineSep string) {
s.out.lineSep = lineSep
s.out.skipSep = true
s.out.indent += 1
}
unindent(s GeneratorState) {
s.out.lineSep = ""
s.out.indent -= 1
}
push(s GeneratorState, cb CodeBuilder) {
prev := s.out
s.out = cb
return prev
}
restore(s GeneratorState, cb CodeBuilder) {
s.out = cb
}
unpackStruct(s GeneratorState, tag Tag) {
assert(tag.kind == TagKind.struct_)
return s.entities[tag.q].as(StructDef)
}
unpackModule(s GeneratorState, tag Tag) {
assert(tag.kind == TagKind.moduleOut)
return s.entities[tag.q].as(ModuleDef)
}
}