📦 Out of Context リポジトリ

リポジトリ / emssr / independent_common_compiler

r2
r2 emssr1
# -*- coding: utf-8 -*-
r2 emssr2
"""平文を綴じて commonc で lane64 の image にし、vcpu.py の lane に配って走らせる。
r2 emssr3
r2 emssr4
doc: commonc.lexicon casm.lane64.host casm.lane64.state
r2 emssr5
r2 emssr6
    image, need = compile([a.cmn, b.cmn], dic, out)   順に綴じて compile(16 式 1.3: 連接が合成)
r2 emssr7
    tab   = load(image)
r2 emssr8
    M     = run(tab, words, {8: 3})                    一枚
r2 emssr9
    heads = LaneWorkers(image, 6).run(words, seeds)    lane ごとに種を変えて配る(20 §1)
r2 emssr10
r2 emssr11
道は一本になる(19 §7.1)。
r2 emssr12
r2 emssr13
    .dic + .cmn ──(commonc_lane64.bin on vcpu.py)──▶ lane64 image ──(vcpu.py)──▶ M
r2 emssr14
r2 emssr15
commonc の image は Haskell が `.cam` から作る。この module は `.cmn` を読まない。
r2 emssr16
r2 emssr17
    common commonc_lane64.cam -t lane64 --no-sim -o builds/commonc/commonc_lane64.bin
r2 emssr18
r2 emssr19
host が commonc へ渡すもの(19 §3.1 と front.cam の割付):
r2 emssr20
r2 emssr21
    語 0    N      入力の byte 数(表 + 本文)
r2 emssr22
    語 1    IB     入力の基底語番号
r2 emssr23
    語 4    B      配列の刻み。表を展開すると音節が byte 数より増えるので host が決める
r2 emssr24
    語 130  LEXB   表の byte 数。表は本文の前に置く
r2 emssr25
r2 emssr26
commonc の lane64 版はワイヤ w を語 64 + w に置く(19 §3.3)。本文の作業領域はその上、
r2 emssr27
WORK = 2^16 から取る。compile 後の n_need が WORK を越えたら止める。
r2 emssr28
r2 emssr29
置き場は `common_systems/Python_vCPU`。実行台(vcpu、vcpu_vec)と同じ階層に置く。
r2 emssr30
借り手は automaton、joinrank、okataduke、LTLS、Joint-Log-Former、kaggriculture の
r2 emssr31
六つで、どれか一つのために書き換えてよい module ではない。
r2 emssr32
"""
r2 emssr33
r2 emssr34
import os
r2 emssr35
import pickle
r2 emssr36
import struct
r2 emssr37
import subprocess
r2 emssr38
import sys
r2 emssr39
r2 emssr40
HERE = os.path.dirname(os.path.abspath(__file__))
r2 emssr41
ROOT = os.path.abspath(os.path.join(HERE, *([os.pardir] * 3)))
r2 emssr42
COMMONC = os.path.join(ROOT, "builds", "commonc", "commonc_lane64.bin")
r2 emssr43
if HERE not in sys.path:
r2 emssr44
    sys.path.insert(0, HERE)
r2 emssr45
import vcpu  # noqa: E402
r2 emssr46
import lex   # noqa: E402
r2 emssr47
r2 emssr48
SEP = "\n\n"
r2 emssr49
WORK = 1 << 16          # 本文の作業領域の基底。ワイヤ(語 64 + w)の上
r2 emssr50
W_B, W_LEXB = 4, 130    # host が commonc へ書く語
r2 emssr51
IN_MUL = 288            # 入力の基底 = IN_MUL · B(back_lane64.cam: 箱あたり 112 byte)
r2 emssr52
r2 emssr53
r2 emssr54
class CompileError(Exception):
r2 emssr55
    pass
r2 emssr56
r2 emssr57
r2 emssr58
def weave(paths, dst):
r2 emssr59
    body = [open(p, encoding="utf-8").read().strip() for p in paths]
r2 emssr60
    open(dst, "w", encoding="utf-8", newline="\n").write(SEP.join(body) + "\n")
r2 emssr61
    return dst
r2 emssr62
r2 emssr63
r2 emssr64
_CC = []
r2 emssr65
r2 emssr66
r2 emssr67
def _commonc():
r2 emssr68
    if not _CC:
r2 emssr69
        if not os.path.exists(COMMONC):
r2 emssr70
            raise CompileError("commonc の image が無い: %s\n"
r2 emssr71
                               "  common commonc_lane64.cam -t lane64 --no-sim -o %s" % (COMMONC, COMMONC))
r2 emssr72
        _CC.append(vcpu.translate(vcpu.decode(open(COMMONC, "rb").read()))[1])
r2 emssr73
    return _CC[0]
r2 emssr74
r2 emssr75
r2 emssr76
def compile(paths, dic, out):
r2 emssr77
    """綴じて commonc で compile する。返りは (image の byte 列, n_need)。
r2 emssr78
r2 emssr79
    dic は `.dic`(見出し ⟨tab⟩ 文)か None。表の byte 数を語 130 に、刻み B を語 4 に置く。
r2 emssr80
    B は展開後の音節数から決める(配列の容量は音節数で決まる。19 §3.2)。
r2 emssr81
    """
r2 emssr82
    os.path.isdir(os.path.dirname(out)) or os.makedirs(os.path.dirname(out))
r2 emssr83
    src = weave(paths, out[:-4] + ".cmn") if len(paths) != 1 else paths[0]
r2 emssr84
    body = open(src, encoding="utf-8").read()
r2 emssr85
    table = lex.lexicon(dic) if dic else ""
r2 emssr86
    data = (table + body).encode("utf-8")
r2 emssr87
    n = len(data)
r2 emssr88
    est = lex.expanded_syllables(dic, body) if dic else n // 2
r2 emssr89
    b = max(n + 80, 4096, 2 * est + 80)
r2 emssr90
    base = IN_MUL * b
r2 emssr91
    lane = vcpu.Lane(base + n + 4096)
r2 emssr92
    lane.put_input(list(data), base)
r2 emssr93
    lane.M[W_B] = b
r2 emssr94
    lane.M[W_LEXB] = len(table.encode("utf-8"))
r2 emssr95
    rep = vcpu.run_lane(_commonc(), lane)
r2 emssr96
    if rep != 0:
r2 emssr97
        raise CompileError("commonc が %s を読めない(報告 %s)" % (src, rep))
r2 emssr98
    imgb, nimg, need = lane.M[1], lane.M[2], lane.M[3]
r2 emssr99
    if not (imgb and nimg):
r2 emssr100
        raise CompileError("commonc が image を出さない: %s" % src)
r2 emssr101
    if need > WORK:
r2 emssr102
        raise CompileError("箱が多すぎてワイヤが作業領域に重なる(n_need %d > WORK %d)" % (need, WORK))
r2 emssr103
    image = bytes(v & 255 for v in lane.M[imgb:imgb + nimg])
r2 emssr104
    open(out, "wb").write(image)
r2 emssr105
    open(out + ".need", "w").write(str(need))
r2 emssr106
    return image, need
r2 emssr107
r2 emssr108
r2 emssr109
def load_file(path):
r2 emssr110
    """compile が置いた image と、その隣の n_need を読む。返りは (image, need)"""
r2 emssr111
    image = open(path, "rb").read()
r2 emssr112
    side = path + ".need"
r2 emssr113
    need = int(open(side).read()) if os.path.exists(side) else None
r2 emssr114
    return image, need
r2 emssr115
r2 emssr116
r2 emssr117
def load(image, need=None):
r2 emssr118
    """image を翻訳する。need(compile が返す n_need)を渡すと、語 64 〜 need のワイヤのうち
r2 emssr119
    他の block が読まない Store を落とす(vcpu.translate の wires)。"""
r2 emssr120
    return vcpu.translate(vcpu.decode(image), wires=(64, need) if need else None)[1]
r2 emssr121
r2 emssr122
r2 emssr123
def run(tab, words, seed):
r2 emssr124
    """一枚走らせて作業領域を返す。報告が tu でなければ例外。"""
r2 emssr125
    lane = vcpu.Lane(words)
r2 emssr126
    for k, v in seed.items():
r2 emssr127
        lane.M[k] = v
r2 emssr128
    rep = vcpu.run_lane(tab, lane)
r2 emssr129
    if rep != 0:
r2 emssr130
        raise RuntimeError("報告 %s" % rep)
r2 emssr131
    return lane.M
r2 emssr132
r2 emssr133
r2 emssr134
# ===== lane を子 process に配る =====
r2 emssr135
#
r2 emssr136
# simulator は agent を daemon の worker で走らせるので、multiprocessing.Pool が作れない。
r2 emssr137
# subprocess で立てた worker に pickle で種を送り、読む語を受け取る。
r2 emssr138
# lane の作業領域は交わらない(20 式 1.2)ので、worker ごとに一枚を使い回し、
r2 emssr139
# 走らせる前に作業領域だけを零に戻す。ワイヤの語は読む前に必ず書かれる。
r2 emssr140
r2 emssr141
r2 emssr142
def _send(f, obj):
r2 emssr143
    data = pickle.dumps(obj, protocol=pickle.HIGHEST_PROTOCOL)
r2 emssr144
    f.write(struct.pack("<Q", len(data)) + data)
r2 emssr145
    f.flush()
r2 emssr146
r2 emssr147
r2 emssr148
def _recv(f):
r2 emssr149
    head = f.read(8)
r2 emssr150
    if len(head) < 8:
r2 emssr151
        raise EOFError("worker が閉じた")
r2 emssr152
    (n,) = struct.unpack("<Q", head)
r2 emssr153
    return pickle.loads(f.read(n))
r2 emssr154
r2 emssr155
r2 emssr156
def _run_seeds(tab, lane, seeds, head, watch):
r2 emssr157
    zero = [0] * (len(lane.M) - WORK)
r2 emssr158
    res = []
r2 emssr159
    for seed in seeds:
r2 emssr160
        lane.M[WORK:] = zero
r2 emssr161
        lane.M[:64] = [0] * 64
r2 emssr162
        for k, v in seed.items():
r2 emssr163
            lane.M[k] = v
r2 emssr164
        rep = vcpu.run_lane(tab, lane)
r2 emssr165
        res.append((rep, lane.M[:head], [lane.M[w] for w in watch]))
r2 emssr166
    return res
r2 emssr167
r2 emssr168
r2 emssr169
# ===== 束ねの道(vcpu_vec)=====
r2 emssr170
#
r2 emssr171
# worker が受け取った seeds を一枚ずつではなく W 枚まとめて走らせる。命題 6.1 が
r2 emssr172
# 根拠になる。W=256 で 7.3 - 7.7 倍、process を P 本立てて式 (0.4) の P 倍が乗る。
r2 emssr173
#
r2 emssr174
# numpy が無い環境では _run_seeds へ落ちる。落ちても観測は変わらない。
r2 emssr175
r2 emssr176
def _run_seeds_vec(tabv, seeds, words, head, watch, chunk, box):
r2 emssr177
    """box は worker の寿命で使い回す LaneVec の置き場。作業領域の確保は
r2 emssr178
    語数 x W x 8 byte を触るので、seeds の組ごとに作り直すと確保だけで束ねの
r2 emssr179
    利得が消える(wither・W=256 の実測で 0.5 倍まで落ちた)。"""
r2 emssr180
    import numpy as np
r2 emssr181
    import vcpu_vec
r2 emssr182
    res = []
r2 emssr183
    for off in range(0, len(seeds), chunk):
r2 emssr184
        part = seeds[off:off + chunk]
r2 emssr185
        lv = box.get(len(part))
r2 emssr186
        if lv is None or lv.M.shape[0] != words:
r2 emssr187
            lv = vcpu_vec.LaneVec(len(part), words)
r2 emssr188
            box[len(part)] = lv
r2 emssr189
        else:
r2 emssr190
            lv.M[WORK:] = 0
r2 emssr191
            lv.M[:64] = 0
r2 emssr192
        for l, seed in enumerate(part):
r2 emssr193
            for k, v in seed.items():
r2 emssr194
                lv.M[k, l] = v
r2 emssr195
        rep = vcpu_vec.run(tabv, lv)
r2 emssr196
        if lv.overflow():
r2 emssr197
            raise OverflowError("語が int64 を越えた。束ねの道へ載せられない")
r2 emssr198
        headw = np.asarray(lv.M[:head]).T.tolist()
r2 emssr199
        watchw = [[int(lv.M[w, l]) for w in watch] for l in range(len(part))]
r2 emssr200
        res.extend(zip(rep, headw, watchw))
r2 emssr201
    return res
r2 emssr202
r2 emssr203
r2 emssr204
def worker_main():
r2 emssr205
    """子 process の本体。最初に (image, need, width) を受け取り、
r2 emssr206
    あとは (words, seeds, head, watch) を繰り返す。
r2 emssr207
r2 emssr208
    width が 0 なら一枚ずつ(翻訳器の道)、正なら W 枚ずつ束ねる。
r2 emssr209
    """
r2 emssr210
    inp, out = sys.stdin.buffer, sys.stdout.buffer
r2 emssr211
    image, need, width = _recv(inp)
r2 emssr212
    tab = load(image, need)
r2 emssr213
    tabv = None
r2 emssr214
    if width:
r2 emssr215
        try:
r2 emssr216
            import vcpu_vec
r2 emssr217
            tabv = vcpu_vec.translate(vcpu.decode(image), wires=(64, need) if need else None)[1]
r2 emssr218
        except ImportError:
r2 emssr219
            tabv = None
r2 emssr220
    lane, box = None, {}
r2 emssr221
    while True:
r2 emssr222
        try:
r2 emssr223
            words, seeds, head, watch = _recv(inp)
r2 emssr224
        except EOFError:
r2 emssr225
            return
r2 emssr226
        if tabv is not None:
r2 emssr227
            try:
r2 emssr228
                _send(out, _run_seeds_vec(tabv, seeds, words, head, watch, width, box))
r2 emssr229
                continue
r2 emssr230
            except OverflowError:
r2 emssr231
                tabv, box = None, {}   # 溢れた image は以後ずっと翻訳器の道で走らせる
r2 emssr232
        if lane is None or len(lane.M) != words:
r2 emssr233
            lane = vcpu.Lane(words)
r2 emssr234
        _send(out, _run_seeds(tab, lane, seeds, head, watch))
r2 emssr235
r2 emssr236
r2 emssr237
class LaneWorkers(object):
r2 emssr238
    """image 一つを持つ worker を procs 本立てて、lane を配る。
r2 emssr239
r2 emssr240
    返りは lane ごとの (報告, 先頭 head 語, watch の語の値)。
r2 emssr241
r2 emssr242
    width を渡すと worker の中で W 枚を束ねる(vcpu_vec)。作業領域は
r2 emssr243
    語数 x width x 8 byte を取るので、語数 225536・width 256 で 462 MB になる。
r2 emssr244
    """
r2 emssr245
r2 emssr246
    def __init__(self, image, procs, need=None, width=0):
r2 emssr247
        self.procs = []
r2 emssr248
        for _ in range(max(1, procs)):
r2 emssr249
            p = subprocess.Popen([sys.executable, os.path.abspath(__file__), "--worker"],
r2 emssr250
                                 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
r2 emssr251
            _send(p.stdin, (image, need, width))
r2 emssr252
            self.procs.append(p)
r2 emssr253
r2 emssr254
    def run(self, words, seeds, head=8, watch=()):
r2 emssr255
        n = len(self.procs)
r2 emssr256
        parts = [seeds[k::n] for k in range(n)]
r2 emssr257
        for p, part in zip(self.procs, parts):
r2 emssr258
            _send(p.stdin, (words, part, head, tuple(watch)))
r2 emssr259
        out = [None] * len(seeds)
r2 emssr260
        for k, p in enumerate(self.procs):
r2 emssr261
            for i, r in enumerate(_recv(p.stdout)):
r2 emssr262
                out[k + i * n] = r
r2 emssr263
        return out
r2 emssr264
r2 emssr265
    def close(self):
r2 emssr266
        for p in self.procs:
r2 emssr267
            try:
r2 emssr268
                p.stdin.close()
r2 emssr269
                p.wait(timeout=5)
r2 emssr270
            except Exception:
r2 emssr271
                p.kill()
r2 emssr272
r2 emssr273
r2 emssr274
if __name__ == "__main__" and "--worker" in sys.argv:
r2 emssr275
    worker_main()