リポジトリ / emssr / independent_common_compiler
r2
| r2 emssr | 1 | # -*- coding: utf-8 -*- |
| r2 emssr | 2 | """平文を綴じて commonc で lane64 の image にし、vcpu.py の lane に配って走らせる。 |
| r2 emssr | 3 | |
| r2 emssr | 4 | doc: commonc.lexicon casm.lane64.host casm.lane64.state |
| r2 emssr | 5 | |
| r2 emssr | 6 | image, need = compile([a.cmn, b.cmn], dic, out) 順に綴じて compile(16 式 1.3: 連接が合成) |
| r2 emssr | 7 | tab = load(image) |
| r2 emssr | 8 | M = run(tab, words, {8: 3}) 一枚 |
| r2 emssr | 9 | heads = LaneWorkers(image, 6).run(words, seeds) lane ごとに種を変えて配る(20 §1) |
| r2 emssr | 10 | |
| r2 emssr | 11 | 道は一本になる(19 §7.1)。 |
| r2 emssr | 12 | |
| r2 emssr | 13 | .dic + .cmn ──(commonc_lane64.bin on vcpu.py)──▶ lane64 image ──(vcpu.py)──▶ M |
| r2 emssr | 14 | |
| r2 emssr | 15 | commonc の image は Haskell が `.cam` から作る。この module は `.cmn` を読まない。 |
| r2 emssr | 16 | |
| r2 emssr | 17 | common commonc_lane64.cam -t lane64 --no-sim -o builds/commonc/commonc_lane64.bin |
| r2 emssr | 18 | |
| r2 emssr | 19 | host が commonc へ渡すもの(19 §3.1 と front.cam の割付): |
| r2 emssr | 20 | |
| r2 emssr | 21 | 語 0 N 入力の byte 数(表 + 本文) |
| r2 emssr | 22 | 語 1 IB 入力の基底語番号 |
| r2 emssr | 23 | 語 4 B 配列の刻み。表を展開すると音節が byte 数より増えるので host が決める |
| r2 emssr | 24 | 語 130 LEXB 表の byte 数。表は本文の前に置く |
| r2 emssr | 25 | |
| r2 emssr | 26 | commonc の lane64 版はワイヤ w を語 64 + w に置く(19 §3.3)。本文の作業領域はその上、 |
| r2 emssr | 27 | WORK = 2^16 から取る。compile 後の n_need が WORK を越えたら止める。 |
| r2 emssr | 28 | |
| r2 emssr | 29 | 置き場は `common_systems/Python_vCPU`。実行台(vcpu、vcpu_vec)と同じ階層に置く。 |
| r2 emssr | 30 | 借り手は automaton、joinrank、okataduke、LTLS、Joint-Log-Former、kaggriculture の |
| r2 emssr | 31 | 六つで、どれか一つのために書き換えてよい module ではない。 |
| r2 emssr | 32 | """ |
| r2 emssr | 33 | |
| r2 emssr | 34 | import os |
| r2 emssr | 35 | import pickle |
| r2 emssr | 36 | import struct |
| r2 emssr | 37 | import subprocess |
| r2 emssr | 38 | import sys |
| r2 emssr | 39 | |
| r2 emssr | 40 | HERE = os.path.dirname(os.path.abspath(__file__)) |
| r2 emssr | 41 | ROOT = os.path.abspath(os.path.join(HERE, *([os.pardir] * 3))) |
| r2 emssr | 42 | COMMONC = os.path.join(ROOT, "builds", "commonc", "commonc_lane64.bin") |
| r2 emssr | 43 | if HERE not in sys.path: |
| r2 emssr | 44 | sys.path.insert(0, HERE) |
| r2 emssr | 45 | import vcpu # noqa: E402 |
| r2 emssr | 46 | import lex # noqa: E402 |
| r2 emssr | 47 | |
| r2 emssr | 48 | SEP = "\n\n" |
| r2 emssr | 49 | WORK = 1 << 16 # 本文の作業領域の基底。ワイヤ(語 64 + w)の上 |
| r2 emssr | 50 | W_B, W_LEXB = 4, 130 # host が commonc へ書く語 |
| r2 emssr | 51 | IN_MUL = 288 # 入力の基底 = IN_MUL · B(back_lane64.cam: 箱あたり 112 byte) |
| r2 emssr | 52 | |
| r2 emssr | 53 | |
| r2 emssr | 54 | class CompileError(Exception): |
| r2 emssr | 55 | pass |
| r2 emssr | 56 | |
| r2 emssr | 57 | |
| r2 emssr | 58 | def weave(paths, dst): |
| r2 emssr | 59 | body = [open(p, encoding="utf-8").read().strip() for p in paths] |
| r2 emssr | 60 | open(dst, "w", encoding="utf-8", newline="\n").write(SEP.join(body) + "\n") |
| r2 emssr | 61 | return dst |
| r2 emssr | 62 | |
| r2 emssr | 63 | |
| r2 emssr | 64 | _CC = [] |
| r2 emssr | 65 | |
| r2 emssr | 66 | |
| r2 emssr | 67 | def _commonc(): |
| r2 emssr | 68 | if not _CC: |
| r2 emssr | 69 | if not os.path.exists(COMMONC): |
| r2 emssr | 70 | raise CompileError("commonc の image が無い: %s\n" |
| r2 emssr | 71 | " common commonc_lane64.cam -t lane64 --no-sim -o %s" % (COMMONC, COMMONC)) |
| r2 emssr | 72 | _CC.append(vcpu.translate(vcpu.decode(open(COMMONC, "rb").read()))[1]) |
| r2 emssr | 73 | return _CC[0] |
| r2 emssr | 74 | |
| r2 emssr | 75 | |
| r2 emssr | 76 | def compile(paths, dic, out): |
| r2 emssr | 77 | """綴じて commonc で compile する。返りは (image の byte 列, n_need)。 |
| r2 emssr | 78 | |
| r2 emssr | 79 | dic は `.dic`(見出し ⟨tab⟩ 文)か None。表の byte 数を語 130 に、刻み B を語 4 に置く。 |
| r2 emssr | 80 | B は展開後の音節数から決める(配列の容量は音節数で決まる。19 §3.2)。 |
| r2 emssr | 81 | """ |
| r2 emssr | 82 | os.path.isdir(os.path.dirname(out)) or os.makedirs(os.path.dirname(out)) |
| r2 emssr | 83 | src = weave(paths, out[:-4] + ".cmn") if len(paths) != 1 else paths[0] |
| r2 emssr | 84 | body = open(src, encoding="utf-8").read() |
| r2 emssr | 85 | table = lex.lexicon(dic) if dic else "" |
| r2 emssr | 86 | data = (table + body).encode("utf-8") |
| r2 emssr | 87 | n = len(data) |
| r2 emssr | 88 | est = lex.expanded_syllables(dic, body) if dic else n // 2 |
| r2 emssr | 89 | b = max(n + 80, 4096, 2 * est + 80) |
| r2 emssr | 90 | base = IN_MUL * b |
| r2 emssr | 91 | lane = vcpu.Lane(base + n + 4096) |
| r2 emssr | 92 | lane.put_input(list(data), base) |
| r2 emssr | 93 | lane.M[W_B] = b |
| r2 emssr | 94 | lane.M[W_LEXB] = len(table.encode("utf-8")) |
| r2 emssr | 95 | rep = vcpu.run_lane(_commonc(), lane) |
| r2 emssr | 96 | if rep != 0: |
| r2 emssr | 97 | raise CompileError("commonc が %s を読めない(報告 %s)" % (src, rep)) |
| r2 emssr | 98 | imgb, nimg, need = lane.M[1], lane.M[2], lane.M[3] |
| r2 emssr | 99 | if not (imgb and nimg): |
| r2 emssr | 100 | raise CompileError("commonc が image を出さない: %s" % src) |
| r2 emssr | 101 | if need > WORK: |
| r2 emssr | 102 | raise CompileError("箱が多すぎてワイヤが作業領域に重なる(n_need %d > WORK %d)" % (need, WORK)) |
| r2 emssr | 103 | image = bytes(v & 255 for v in lane.M[imgb:imgb + nimg]) |
| r2 emssr | 104 | open(out, "wb").write(image) |
| r2 emssr | 105 | open(out + ".need", "w").write(str(need)) |
| r2 emssr | 106 | return image, need |
| r2 emssr | 107 | |
| r2 emssr | 108 | |
| r2 emssr | 109 | def load_file(path): |
| r2 emssr | 110 | """compile が置いた image と、その隣の n_need を読む。返りは (image, need)""" |
| r2 emssr | 111 | image = open(path, "rb").read() |
| r2 emssr | 112 | side = path + ".need" |
| r2 emssr | 113 | need = int(open(side).read()) if os.path.exists(side) else None |
| r2 emssr | 114 | return image, need |
| r2 emssr | 115 | |
| r2 emssr | 116 | |
| r2 emssr | 117 | def load(image, need=None): |
| r2 emssr | 118 | """image を翻訳する。need(compile が返す n_need)を渡すと、語 64 〜 need のワイヤのうち |
| r2 emssr | 119 | 他の block が読まない Store を落とす(vcpu.translate の wires)。""" |
| r2 emssr | 120 | return vcpu.translate(vcpu.decode(image), wires=(64, need) if need else None)[1] |
| r2 emssr | 121 | |
| r2 emssr | 122 | |
| r2 emssr | 123 | def run(tab, words, seed): |
| r2 emssr | 124 | """一枚走らせて作業領域を返す。報告が tu でなければ例外。""" |
| r2 emssr | 125 | lane = vcpu.Lane(words) |
| r2 emssr | 126 | for k, v in seed.items(): |
| r2 emssr | 127 | lane.M[k] = v |
| r2 emssr | 128 | rep = vcpu.run_lane(tab, lane) |
| r2 emssr | 129 | if rep != 0: |
| r2 emssr | 130 | raise RuntimeError("報告 %s" % rep) |
| r2 emssr | 131 | return lane.M |
| r2 emssr | 132 | |
| r2 emssr | 133 | |
| r2 emssr | 134 | # ===== lane を子 process に配る ===== |
| r2 emssr | 135 | # |
| r2 emssr | 136 | # simulator は agent を daemon の worker で走らせるので、multiprocessing.Pool が作れない。 |
| r2 emssr | 137 | # subprocess で立てた worker に pickle で種を送り、読む語を受け取る。 |
| r2 emssr | 138 | # lane の作業領域は交わらない(20 式 1.2)ので、worker ごとに一枚を使い回し、 |
| r2 emssr | 139 | # 走らせる前に作業領域だけを零に戻す。ワイヤの語は読む前に必ず書かれる。 |
| r2 emssr | 140 | |
| r2 emssr | 141 | |
| r2 emssr | 142 | def _send(f, obj): |
| r2 emssr | 143 | data = pickle.dumps(obj, protocol=pickle.HIGHEST_PROTOCOL) |
| r2 emssr | 144 | f.write(struct.pack("<Q", len(data)) + data) |
| r2 emssr | 145 | f.flush() |
| r2 emssr | 146 | |
| r2 emssr | 147 | |
| r2 emssr | 148 | def _recv(f): |
| r2 emssr | 149 | head = f.read(8) |
| r2 emssr | 150 | if len(head) < 8: |
| r2 emssr | 151 | raise EOFError("worker が閉じた") |
| r2 emssr | 152 | (n,) = struct.unpack("<Q", head) |
| r2 emssr | 153 | return pickle.loads(f.read(n)) |
| r2 emssr | 154 | |
| r2 emssr | 155 | |
| r2 emssr | 156 | def _run_seeds(tab, lane, seeds, head, watch): |
| r2 emssr | 157 | zero = [0] * (len(lane.M) - WORK) |
| r2 emssr | 158 | res = [] |
| r2 emssr | 159 | for seed in seeds: |
| r2 emssr | 160 | lane.M[WORK:] = zero |
| r2 emssr | 161 | lane.M[:64] = [0] * 64 |
| r2 emssr | 162 | for k, v in seed.items(): |
| r2 emssr | 163 | lane.M[k] = v |
| r2 emssr | 164 | rep = vcpu.run_lane(tab, lane) |
| r2 emssr | 165 | res.append((rep, lane.M[:head], [lane.M[w] for w in watch])) |
| r2 emssr | 166 | return res |
| r2 emssr | 167 | |
| r2 emssr | 168 | |
| r2 emssr | 169 | # ===== 束ねの道(vcpu_vec)===== |
| r2 emssr | 170 | # |
| r2 emssr | 171 | # worker が受け取った seeds を一枚ずつではなく W 枚まとめて走らせる。命題 6.1 が |
| r2 emssr | 172 | # 根拠になる。W=256 で 7.3 - 7.7 倍、process を P 本立てて式 (0.4) の P 倍が乗る。 |
| r2 emssr | 173 | # |
| r2 emssr | 174 | # numpy が無い環境では _run_seeds へ落ちる。落ちても観測は変わらない。 |
| r2 emssr | 175 | |
| r2 emssr | 176 | def _run_seeds_vec(tabv, seeds, words, head, watch, chunk, box): |
| r2 emssr | 177 | """box は worker の寿命で使い回す LaneVec の置き場。作業領域の確保は |
| r2 emssr | 178 | 語数 x W x 8 byte を触るので、seeds の組ごとに作り直すと確保だけで束ねの |
| r2 emssr | 179 | 利得が消える(wither・W=256 の実測で 0.5 倍まで落ちた)。""" |
| r2 emssr | 180 | import numpy as np |
| r2 emssr | 181 | import vcpu_vec |
| r2 emssr | 182 | res = [] |
| r2 emssr | 183 | for off in range(0, len(seeds), chunk): |
| r2 emssr | 184 | part = seeds[off:off + chunk] |
| r2 emssr | 185 | lv = box.get(len(part)) |
| r2 emssr | 186 | if lv is None or lv.M.shape[0] != words: |
| r2 emssr | 187 | lv = vcpu_vec.LaneVec(len(part), words) |
| r2 emssr | 188 | box[len(part)] = lv |
| r2 emssr | 189 | else: |
| r2 emssr | 190 | lv.M[WORK:] = 0 |
| r2 emssr | 191 | lv.M[:64] = 0 |
| r2 emssr | 192 | for l, seed in enumerate(part): |
| r2 emssr | 193 | for k, v in seed.items(): |
| r2 emssr | 194 | lv.M[k, l] = v |
| r2 emssr | 195 | rep = vcpu_vec.run(tabv, lv) |
| r2 emssr | 196 | if lv.overflow(): |
| r2 emssr | 197 | raise OverflowError("語が int64 を越えた。束ねの道へ載せられない") |
| r2 emssr | 198 | headw = np.asarray(lv.M[:head]).T.tolist() |
| r2 emssr | 199 | watchw = [[int(lv.M[w, l]) for w in watch] for l in range(len(part))] |
| r2 emssr | 200 | res.extend(zip(rep, headw, watchw)) |
| r2 emssr | 201 | return res |
| r2 emssr | 202 | |
| r2 emssr | 203 | |
| r2 emssr | 204 | def worker_main(): |
| r2 emssr | 205 | """子 process の本体。最初に (image, need, width) を受け取り、 |
| r2 emssr | 206 | あとは (words, seeds, head, watch) を繰り返す。 |
| r2 emssr | 207 | |
| r2 emssr | 208 | width が 0 なら一枚ずつ(翻訳器の道)、正なら W 枚ずつ束ねる。 |
| r2 emssr | 209 | """ |
| r2 emssr | 210 | inp, out = sys.stdin.buffer, sys.stdout.buffer |
| r2 emssr | 211 | image, need, width = _recv(inp) |
| r2 emssr | 212 | tab = load(image, need) |
| r2 emssr | 213 | tabv = None |
| r2 emssr | 214 | if width: |
| r2 emssr | 215 | try: |
| r2 emssr | 216 | import vcpu_vec |
| r2 emssr | 217 | tabv = vcpu_vec.translate(vcpu.decode(image), wires=(64, need) if need else None)[1] |
| r2 emssr | 218 | except ImportError: |
| r2 emssr | 219 | tabv = None |
| r2 emssr | 220 | lane, box = None, {} |
| r2 emssr | 221 | while True: |
| r2 emssr | 222 | try: |
| r2 emssr | 223 | words, seeds, head, watch = _recv(inp) |
| r2 emssr | 224 | except EOFError: |
| r2 emssr | 225 | return |
| r2 emssr | 226 | if tabv is not None: |
| r2 emssr | 227 | try: |
| r2 emssr | 228 | _send(out, _run_seeds_vec(tabv, seeds, words, head, watch, width, box)) |
| r2 emssr | 229 | continue |
| r2 emssr | 230 | except OverflowError: |
| r2 emssr | 231 | tabv, box = None, {} # 溢れた image は以後ずっと翻訳器の道で走らせる |
| r2 emssr | 232 | if lane is None or len(lane.M) != words: |
| r2 emssr | 233 | lane = vcpu.Lane(words) |
| r2 emssr | 234 | _send(out, _run_seeds(tab, lane, seeds, head, watch)) |
| r2 emssr | 235 | |
| r2 emssr | 236 | |
| r2 emssr | 237 | class LaneWorkers(object): |
| r2 emssr | 238 | """image 一つを持つ worker を procs 本立てて、lane を配る。 |
| r2 emssr | 239 | |
| r2 emssr | 240 | 返りは lane ごとの (報告, 先頭 head 語, watch の語の値)。 |
| r2 emssr | 241 | |
| r2 emssr | 242 | width を渡すと worker の中で W 枚を束ねる(vcpu_vec)。作業領域は |
| r2 emssr | 243 | 語数 x width x 8 byte を取るので、語数 225536・width 256 で 462 MB になる。 |
| r2 emssr | 244 | """ |
| r2 emssr | 245 | |
| r2 emssr | 246 | def __init__(self, image, procs, need=None, width=0): |
| r2 emssr | 247 | self.procs = [] |
| r2 emssr | 248 | for _ in range(max(1, procs)): |
| r2 emssr | 249 | p = subprocess.Popen([sys.executable, os.path.abspath(__file__), "--worker"], |
| r2 emssr | 250 | stdin=subprocess.PIPE, stdout=subprocess.PIPE) |
| r2 emssr | 251 | _send(p.stdin, (image, need, width)) |
| r2 emssr | 252 | self.procs.append(p) |
| r2 emssr | 253 | |
| r2 emssr | 254 | def run(self, words, seeds, head=8, watch=()): |
| r2 emssr | 255 | n = len(self.procs) |
| r2 emssr | 256 | parts = [seeds[k::n] for k in range(n)] |
| r2 emssr | 257 | for p, part in zip(self.procs, parts): |
| r2 emssr | 258 | _send(p.stdin, (words, part, head, tuple(watch))) |
| r2 emssr | 259 | out = [None] * len(seeds) |
| r2 emssr | 260 | for k, p in enumerate(self.procs): |
| r2 emssr | 261 | for i, r in enumerate(_recv(p.stdout)): |
| r2 emssr | 262 | out[k + i * n] = r |
| r2 emssr | 263 | return out |
| r2 emssr | 264 | |
| r2 emssr | 265 | def close(self): |
| r2 emssr | 266 | for p in self.procs: |
| r2 emssr | 267 | try: |
| r2 emssr | 268 | p.stdin.close() |
| r2 emssr | 269 | p.wait(timeout=5) |
| r2 emssr | 270 | except Exception: |
| r2 emssr | 271 | p.kill() |
| r2 emssr | 272 | |
| r2 emssr | 273 | |
| r2 emssr | 274 | if __name__ == "__main__" and "--worker" in sys.argv: |
| r2 emssr | 275 | worker_main() |