|
| 1 | +// xc codegen — generic interface monomorphization. (spliced via xc.xi) |
| 2 | + |
| 3 | +// Rebuild a Program swapping only its interface + class lists (the monomorphized |
| 4 | +// sets); every other field is carried through unchanged. |
| 5 | +mapper Program.withMono(ifaces: IfaceSpec[], classes: ClassSpec[]) -> Program { |
| 6 | + return Program { |
| 7 | + types: this.types, ifaces: ifaces, classes: classes, |
| 8 | + modules: this.modules, functions: this.functions, externs: this.externs, |
| 9 | + entrySpec: this.entrySpec, interrupts: this.interrupts, atoms: this.atoms, |
| 10 | + machines: this.machines, eventTypes: this.eventTypes, tables: this.tables, |
| 11 | + tests: this.tests, scheduled: this.scheduled, libraries: this.libraries, |
| 12 | + infixFns: this.infixFns, cIncludes: this.cIncludes, cFlags: this.cFlags |
| 13 | + } |
| 14 | +} |
| 15 | + |
| 16 | +// |
| 17 | +// A generic interface (typeParams non-empty) is a template. For each class that |
| 18 | +// implements a concrete instantiation `Base<A1, A2>`, we synthesize a concrete |
| 19 | +// non-generic interface with the type parameters textually substituted, so all |
| 20 | +// the existing vtable / caster / default-method codegen works unchanged. The |
| 21 | +// class's `implements` entry is rewritten to the mangled concrete name, and any |
| 22 | +// interfaces the template `extends` are flattened in (their substituted methods |
| 23 | +// copied down) and added to the class so casts to a base interface also work. |
| 24 | +// |
| 25 | +// This runs once, right after parsing, producing a Program with the generic |
| 26 | +// templates removed and the concrete instantiations added. |
| 27 | + |
| 28 | +// "Integer,User" -> ["Integer","User"] ("" -> []) |
| 29 | +mapper splitCsv(s: String) -> String[] { |
| 30 | + let out: String[] = [] |
| 31 | + if string_len(s) == 0 { return out } |
| 32 | + let start = 0 |
| 33 | + let i = 0 |
| 34 | + let n = string_len(s) |
| 35 | + while i <= n { |
| 36 | + let atSep = i == n |
| 37 | + if not atSep { if string_char_at(s, i) == 44 { atSep = true } } // ',' |
| 38 | + if atSep { |
| 39 | + out = appendString(out, string_slice(s, start, i)) |
| 40 | + start = i + 1 |
| 41 | + } |
| 42 | + i = i + 1 |
| 43 | + } |
| 44 | + return out |
| 45 | +} |
| 46 | + |
| 47 | +// mangled concrete interface name: Base<Integer,User> -> Base_Integer_User |
| 48 | +mapper monoName(base: String, argsCsv: String) -> String { |
| 49 | + return base + "_" + argsCsv.replaceAll(",", "_") |
| 50 | +} |
| 51 | + |
| 52 | +// The ctype-suffix form of an Xi type name (Integer -> integer, User -> User), |
| 53 | +// so substitution into a ctype string produces the right primitive casing. |
| 54 | +mapper ctypeSuffixOf(xname: String) -> String { |
| 55 | + let ct = xname.xnameToCtype() |
| 56 | + if ct.startsWith2("xc_") and ct.endsWith2("_t") { return string_slice(ct, 3, string_len(ct) - 2) } |
| 57 | + return xname |
| 58 | +} |
| 59 | + |
| 60 | +// substitute each type param -> its arg inside a ctype / params string. Type |
| 61 | +// vars in a ctype are always underscore-bounded (`xc_T_t`, `xc_opt_T_t`, |
| 62 | +// `xc_arr_T_t`), so match `_<param>_` to avoid clobbering an unrelated name that |
| 63 | +// merely contains the param as a substring (e.g. T inside Timestamp). The arg is |
| 64 | +// substituted in its ctype-suffix form (Integer -> integer). |
| 65 | +mapper substStr(s: String, params: String[], args: String[]) -> String { |
| 66 | + let out = s |
| 67 | + let i = 0 |
| 68 | + while i < stringArrLen(params) { |
| 69 | + if i < stringArrLen(args) { |
| 70 | + out = out.replaceAll("_" + stringArrGet(params, i) + "_", "_" + ctypeSuffixOf(stringArrGet(args, i)) + "_") |
| 71 | + } |
| 72 | + i = i + 1 |
| 73 | + } |
| 74 | + return out |
| 75 | +} |
| 76 | + |
| 77 | +// substitute type params in a method's default-body tokens (identifier text) |
| 78 | +mapper substTokens(toks: Token[], params: String[], args: String[]) -> Token[] { |
| 79 | + let out: Token[] = [] |
| 80 | + let i = 0 |
| 81 | + let n = tokenArrLen(toks) |
| 82 | + while i < n { |
| 83 | + let t = tokenArrGet(toks, i) |
| 84 | + let txt = t.text |
| 85 | + if t.kind == 1 { |
| 86 | + let p = 0 |
| 87 | + while p < stringArrLen(params) { |
| 88 | + if txt == stringArrGet(params, p) and p < stringArrLen(args) { txt = stringArrGet(args, p) } |
| 89 | + p = p + 1 |
| 90 | + } |
| 91 | + } |
| 92 | + out = appendToken(out, Token { kind: t.kind, text: txt, line: t.line, file: t.file }) |
| 93 | + i = i + 1 |
| 94 | + } |
| 95 | + return out |
| 96 | +} |
| 97 | + |
| 98 | +mapper substMethod(ms: MethodSpec, params: String[], args: String[]) -> MethodSpec { |
| 99 | + return MethodSpec { |
| 100 | + isAsync: ms.isAsync, kind: ms.kind, name: ms.name, |
| 101 | + params: substStr(ms.params, params, args), |
| 102 | + retCtype: substStr(ms.retCtype, params, args), |
| 103 | + bodyTokens: substTokens(ms.bodyTokens, params, args), |
| 104 | + topic: ms.topic, hasWhere: ms.hasWhere, |
| 105 | + whereTokens: substTokens(ms.whereTokens, params, args), |
| 106 | + fnDeps: ms.fnDeps |
| 107 | + } |
| 108 | +} |
| 109 | + |
| 110 | +// Substitute type params in an args list "TKey,TEntity" -> "Integer,User" — a |
| 111 | +// name-level (not ctype) substitution, used for an extended interface's args |
| 112 | +// which feed the mangled name. |
| 113 | +mapper substArgs(argsCsv: String, params: String[], args: String[]) -> String { |
| 114 | + let parts = splitCsv(argsCsv) |
| 115 | + let out = "" |
| 116 | + let i = 0 |
| 117 | + while i < stringArrLen(parts) { |
| 118 | + let repl = stringArrGet(parts, i) |
| 119 | + let j = 0 |
| 120 | + while j < stringArrLen(params) { |
| 121 | + if repl == stringArrGet(params, j) and j < stringArrLen(args) { repl = stringArrGet(args, j) } |
| 122 | + j = j + 1 |
| 123 | + } |
| 124 | + if string_len(out) > 0 { out = out + "," } |
| 125 | + out = out + repl |
| 126 | + i = i + 1 |
| 127 | + } |
| 128 | + return out |
| 129 | +} |
| 130 | + |
| 131 | +mapper findGenericIface(prog: Program, name: String) -> IfaceSpec { |
| 132 | + let i = 0 |
| 133 | + let n = ifaceSpecLen(prog.ifaces) |
| 134 | + while i < n { |
| 135 | + let is2 = ifaceSpecGet(prog.ifaces, i) |
| 136 | + if is2.name == name { return is2 } |
| 137 | + i = i + 1 |
| 138 | + } |
| 139 | + return IfaceSpec { name: "", extendsNames: [], methList: [], typeParams: [], extendsArgs: [] } |
| 140 | +} |
| 141 | + |
| 142 | +predicate ifaceAccHas(acc: IfaceSpec[], name: String) { |
| 143 | + let d = 0 |
| 144 | + while d < ifaceSpecLen(acc) { if ifaceSpecGet(acc, d).name == name { return true } d = d + 1 } |
| 145 | + return false |
| 146 | +} |
| 147 | + |
| 148 | +// Synthesize the concrete interface for Base<argsCsv> plus every generic |
| 149 | +// interface it (transitively) extends, appending them to `acc` (deduped). The |
| 150 | +// concrete methList flattens in the substituted methods of extended interfaces. |
| 151 | +mapper synthIface(prog: Program, base: String, argsCsv: String, acc: IfaceSpec[]) -> IfaceSpec[] { |
| 152 | + let mangled = monoName(base, argsCsv) |
| 153 | + if ifaceAccHas(acc, mangled) { return acc } |
| 154 | + |
| 155 | + let tmpl = findGenericIface(prog, base) |
| 156 | + let params = tmpl.typeParams |
| 157 | + let args = splitCsv(argsCsv) |
| 158 | + |
| 159 | + // this interface's own (substituted) methods |
| 160 | + let methList: MethodSpec[] = [] |
| 161 | + let mi = 0 |
| 162 | + while mi < methodSpecLen(tmpl.methList) { |
| 163 | + methList = appendMethodSpec(methList, substMethod(methodSpecGet(tmpl.methList, mi), params, args)) |
| 164 | + mi = mi + 1 |
| 165 | + } |
| 166 | + // flatten extended interfaces: substitute their args, synth them, copy methods down |
| 167 | + let exNames: String[] = [] |
| 168 | + let ei = 0 |
| 169 | + while ei < stringArrLen(tmpl.extendsNames) { |
| 170 | + let bn = stringArrGet(tmpl.extendsNames, ei) |
| 171 | + let ba = "" |
| 172 | + if ei < stringArrLen(tmpl.extendsArgs) { ba = substArgs(stringArrGet(tmpl.extendsArgs, ei), params, args) } |
| 173 | + if string_len(ba) > 0 { |
| 174 | + let baseM = monoName(bn, ba) |
| 175 | + exNames = appendString(exNames, baseM) |
| 176 | + acc = synthIface(prog, bn, ba, acc) |
| 177 | + // copy the base's (already substituted) methods into this vtable |
| 178 | + let bi = 0 |
| 179 | + while bi < ifaceSpecLen(acc) { |
| 180 | + let cand = ifaceSpecGet(acc, bi) |
| 181 | + if cand.name == baseM { |
| 182 | + let cm = 0 |
| 183 | + while cm < methodSpecLen(cand.methList) { |
| 184 | + methList = appendMethodSpec(methList, methodSpecGet(cand.methList, cm)) |
| 185 | + cm = cm + 1 |
| 186 | + } |
| 187 | + } |
| 188 | + bi = bi + 1 |
| 189 | + } |
| 190 | + } else { |
| 191 | + exNames = appendString(exNames, bn) |
| 192 | + } |
| 193 | + ei = ei + 1 |
| 194 | + } |
| 195 | + let concrete = IfaceSpec { name: mangled, extendsNames: exNames, methList: methList, |
| 196 | + typeParams: [], extendsArgs: [] } |
| 197 | + return appendIfaceSpec(acc, concrete) |
| 198 | +} |
| 199 | + |
| 200 | +// All concrete interface names (self + transitive generic bases) for Base<args>. |
| 201 | +mapper monoChain(prog: Program, base: String, argsCsv: String, out: String[]) -> String[] { |
| 202 | + let mangled = monoName(base, argsCsv) |
| 203 | + if strArrContains(out, mangled) { return out } |
| 204 | + out = appendString(out, mangled) |
| 205 | + let tmpl = findGenericIface(prog, base) |
| 206 | + let ei = 0 |
| 207 | + while ei < stringArrLen(tmpl.extendsNames) { |
| 208 | + let ba = "" |
| 209 | + if ei < stringArrLen(tmpl.extendsArgs) { |
| 210 | + ba = substArgs(stringArrGet(tmpl.extendsArgs, ei), tmpl.typeParams, splitCsv(argsCsv)) |
| 211 | + } |
| 212 | + if string_len(ba) > 0 { out = monoChain(prog, stringArrGet(tmpl.extendsNames, ei), ba, out) } |
| 213 | + ei = ei + 1 |
| 214 | + } |
| 215 | + return out |
| 216 | +} |
| 217 | + |
| 218 | +predicate Program.isGenericIfaceName(name: String) { |
| 219 | + let i = 0 |
| 220 | + let n = ifaceSpecLen(this.ifaces) |
| 221 | + while i < n { |
| 222 | + let is2 = ifaceSpecGet(this.ifaces, i) |
| 223 | + if is2.name == name and stringArrLen(is2.typeParams) > 0 { return true } |
| 224 | + i = i + 1 |
| 225 | + } |
| 226 | + return false |
| 227 | +} |
| 228 | + |
| 229 | +// The whole pass: rewrite classes' generic implements to concrete names and |
| 230 | +// return a Program whose ifaces = (non-generic originals) + (synthesized). |
| 231 | +mapper monomorphize(prog: Program) -> Program { |
| 232 | + let synth: IfaceSpec[] = [] |
| 233 | + let newClasses: ClassSpec[] = [] |
| 234 | + |
| 235 | + let ci = 0 |
| 236 | + while ci < classSpecLen(prog.classes) { |
| 237 | + let cs = classSpecGet(prog.classes, ci) |
| 238 | + let newImpl: String[] = [] |
| 239 | + let ii = 0 |
| 240 | + while ii < stringArrLen(cs.implNames) { |
| 241 | + let ifn = stringArrGet(cs.implNames, ii) |
| 242 | + let arg = "" |
| 243 | + if ii < stringArrLen(cs.implArgs) { arg = stringArrGet(cs.implArgs, ii) } |
| 244 | + if string_len(arg) > 0 and prog.isGenericIfaceName(ifn) { |
| 245 | + // this + every generic base become concrete implemented interfaces |
| 246 | + let chain = monoChain(prog, ifn, arg, emptyStrings()) |
| 247 | + let k = 0 |
| 248 | + while k < stringArrLen(chain) { |
| 249 | + if not strArrContains(newImpl, stringArrGet(chain, k)) { |
| 250 | + newImpl = appendString(newImpl, stringArrGet(chain, k)) |
| 251 | + } |
| 252 | + k = k + 1 |
| 253 | + } |
| 254 | + synth = synthIface(prog, ifn, arg, synth) |
| 255 | + } else { |
| 256 | + newImpl = appendString(newImpl, ifn) |
| 257 | + } |
| 258 | + ii = ii + 1 |
| 259 | + } |
| 260 | + newClasses = appendClassSpec(newClasses, ClassSpec { |
| 261 | + name: cs.name, implNames: newImpl, depList: cs.depList, methList: cs.methList, |
| 262 | + stateFields: cs.stateFields, stateInit: cs.stateInit, implArgs: cs.implArgs |
| 263 | + }) |
| 264 | + ci = ci + 1 |
| 265 | + } |
| 266 | + |
| 267 | + // final ifaces: keep non-generic originals, drop templates, add synthesized |
| 268 | + let newIfaces: IfaceSpec[] = [] |
| 269 | + let fi = 0 |
| 270 | + while fi < ifaceSpecLen(prog.ifaces) { |
| 271 | + let is2 = ifaceSpecGet(prog.ifaces, fi) |
| 272 | + if stringArrLen(is2.typeParams) == 0 { newIfaces = appendIfaceSpec(newIfaces, is2) } |
| 273 | + fi = fi + 1 |
| 274 | + } |
| 275 | + let si = 0 |
| 276 | + while si < ifaceSpecLen(synth) { |
| 277 | + newIfaces = appendIfaceSpec(newIfaces, ifaceSpecGet(synth, si)) |
| 278 | + si = si + 1 |
| 279 | + } |
| 280 | + |
| 281 | + return prog.withMono(newIfaces, newClasses) |
| 282 | +} |
0 commit comments