Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions foundation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,15 @@ src32_testbench /absolute/path/target/foundation-wax/test.wax 42 --cpu pipeline

`librle.wal`(`lib/rle`、image ID `rle`、ABI 1.0)は PackBits 形式の RLE で、`/lib/rle.1.wal` として既定のディスクに入ります。heap・libfnd・作業領域を使わず、4 byte 単位でラン検出・コピー・埋め込みを行います。テストは `python3 -m unittest foundation.tests.rle.test_rle`(Clang と `src32_testbench` が必要。後者がなければ Foundation 上のテストは skip)です。

### 文字コード変換 libcode

`libcode.wal`(`lib/code`、image ID `code`、ABI 1.0、`/lib/code.1.wal`)は UTF-8(BOM あり/なし)、UTF-16LE/BE、UTF-32LE/BE を変換します。UTF-32BE を基準の形式とし、`cpt_code_<enc>_to_utf32be` と `cpt_code_utf32be_to_<enc>`(`<enc>` は `utf8`、`utf8bom`、`utf16le`、`utf16be`、`utf32le`)の対で変換します。他の符号化どうしは UTF-32BE を経由します。引数・戻り値・`*dst_size` は conde と同じ形(`flags` を追加)で、規約は [`include/foundation/code.h`](include/foundation/code.h) にあります。

- 不正な列(過長形式、サロゲート、U+10FFFF 超、対にならないサロゲート)は `CONDE_DATA`、末尾で切れた列は `CONDE_INPUT_EOF`。`CODE_REPLACE` を付けると不正な部分(maximal subpart)ごとに U+FFFD を書いて続けます。
- BOM: `utf8bom` への変換は BOM を必ず付け、からの変換は先頭の BOM を読み飛ばします。他の符号化には `CODE_ADD_BOM`/`CODE_STRIP_BOM`。BOM から符号化を調べるのは `cpt_code_detect`(`FF FE 00 00` は UTF-32LE)。
- 出力の見積もりは `cpt_code_bound`。heap・libfnd・作業領域は使いません。
- テストは `python3 -m unittest foundation.tests.code.test_code`(ホストは Clang、Foundation 上は `src32_testbench` も必要)です。

`tests/wal/` は、`tools/llvm-src32` の `--wal`、`--wax3`、`--import-wal`([ツールチェーンの README](../tools/llvm-src32/README.md#wax-v3--wal))で C から作った画像を、カーネルで実行します。`build.py` が `test.base`(WAL)、それを import する `test.math`(WAL、1.2)、両方を import する `test.app`(WAX v3、スロット5と6)を作り、`images.h` に書き出します。`app.c` は、WAL どうしの呼び出し、WAL 内部の関数ポインタ、WAL の静的データがプロセスごとに別であること、オブジェクトの import、文字列定数のアドレス、64-bit の引数と戻り値を確認し、すべて通れば 42 で終了します(起動コードが `proc_exit` を呼ぶ)。

```sh
Expand Down
9 changes: 8 additions & 1 deletion foundation/boot/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
CPUID reports V (doc/foundation_vm_plan.md).

Outputs:
root/ /bin/*.wax (the shell and the commands), /lib/fnd.1.wal, /lib/lzma.1.wal, /lib/rle.1.wal and /lib/sgc.1.wal
root/ /bin/*.wax (the shell and the commands), /lib/fnd.1.wal, /lib/lzma.1.wal, /lib/rle.1.wal, /lib/code.1.wal and /lib/sgc.1.wal
foundation.wax kernel + module-only FNDBOOT as a block at 0x200000; the host
loads it directly (§11 without stage1) and fs mounts IDC slot 0 as "/"
kernel.bin kernel raw image for 0x00010000, entered by stage1
Expand Down Expand Up @@ -99,6 +99,7 @@
# Commands that import /lib/sgc.1.wal (libsgc) as well as fnd.
SGC_COMMANDS = {"chip8"}
RLE_DIR = FOUNDATION / "lib/rle"
CODE_DIR = FOUNDATION / "lib/code"
FS = [FOUNDATION / "fs" / name for name in ("main.c", "fs2.c", "vfs.c", "server.c", "dsfs.c")]
# KAN-38 temporary boot mitigation. Set to 1 to restore the full §10 scan.
FS2_FULL_MOUNT_CHECK = 0
Expand Down Expand Up @@ -203,6 +204,11 @@ def main(argv=None):
rle_build = importlib.util.module_from_spec(rle_spec)
rle_spec.loader.exec_module(rle_build)
rle_job = pool.submit(rle_build.build, out / "rle", args.clang, args.optimization)
# /lib/code.1.wal: character code conversion (lib/code, WAL "code" 1.0), also self-contained.
code_spec = importlib.util.spec_from_file_location("code_build", CODE_DIR / "build.py")
code_build = importlib.util.module_from_spec(code_spec)
code_spec.loader.exec_module(code_build)
code_job = pool.submit(code_build.build, out / "code", args.clang, args.optimization)

kernel_args = ["--origin", hex(KERNEL_LOAD), "--stack-top", hex(KERNEL_STACK_TOP)]
if args.stop_ticks is not None:
Expand Down Expand Up @@ -252,6 +258,7 @@ def main(argv=None):
shutil.copyfile(lzma_job.result(), root / "lib/lzma.1.wal")
shutil.copyfile(sgc_job.result(), root / "lib/sgc.1.wal")
shutil.copyfile(rle_job.result(), root / "lib/rle.1.wal")
shutil.copyfile(code_job.result(), root / "lib/code.1.wal")
for job in program_jobs:
job.result()
modules = [dict(spec, image=spec["image"].result().read_bytes()) for spec in specs]
Expand Down
64 changes: 64 additions & 0 deletions foundation/include/foundation/code.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#ifndef FOUNDATION_CODE_H
#define FOUNDATION_CODE_H
#include <foundation/conde.h>

/* 文字コード変換。UTF-32BE(コードポイントを 4 byte big endian で並べたもの)を基準の形式とし、
* 他の符号化との間を cpt_code_<enc>_to_utf32be と cpt_code_utf32be_to_<enc> で変換します。
* <enc> は utf8、utf8bom、utf16le、utf16be、utf32le。たとえば UTF-16LE から UTF-8 へは
* utf16le_to_utf32be の出力を utf32be_to_utf8 に渡します(UTF-32BE の内容を直接扱う場合は変換が要りません)。
* 戻り値は CONDE_*(0 が成功)、*dst_size は容量を入力して実長を返し、失敗時は 0 にします(conde と同じ)。
* 作業領域は使いません。dst と src は重ねないでください。
*
* int32_t cpt_code_<enc>_to_utf32be(uint8_t *dst, uint32_t *dst_size,
* const uint8_t *src, uint32_t src_size, uint32_t flags);
* int32_t cpt_code_utf32be_to_<enc>(同じ引数);
*
* utf8bom は UTF-8 の BOM 付き。変換先では先頭に EF BB BF を必ず書き、変換元では先頭にあれば読み飛ばす。
* 出力に BOM を付けたい他の符号化には CODE_ADD_BOM を使う。
*
* エラー:
* CONDE_DATA 不正な列。UTF-8 の過長形式・サロゲート・U+10FFFF 超、UTF-16 の対にならないサロゲート、
* UTF-32 のサロゲート・U+10FFFF 超
* CONDE_INPUT_EOF 末尾で符号化単位が途中で切れている(UTF-8 の列の途中、UTF-16 の上位サロゲートだけ、
* 端数 byte など)
* CONDE_OUTPUT_EOF 出力が *dst_size に収まらない
* CONDE_PARAMETER flags が不正、ポインタが NULL(src_size == 0 なら src は NULL 可)
* CODE_REPLACE では、不正な部分(Unicode 3.9 の maximal subpart)ごとに U+FFFD を 1 つ書いて続ける。
* 先頭の U+FEFF は、CODE_STRIP_BOM などで落とさない限り文字として写す(UTF-16LE の FF FE は ZWNBSP になる)。
* CODE_ADD_BOM と入力側の U+FEFF が重なると BOM が 2 つになる。BOM を整えるなら CODE_STRIP_BOM と組み合わせる。 */
#define CODE_REPLACE 1u /* 不正な入力を U+FFFD に置き換える(なければ CONDE_DATA/CONDE_INPUT_EOF) */
#define CODE_STRIP_BOM 2u /* 入力の先頭の U+FEFF を捨てる(utf8bom の変換元は指定なしでも読み飛ばす) */
#define CODE_ADD_BOM 4u /* 出力の先頭に BOM を付ける(utf8bom の変換先は指定なしでも付く) */

#define CPT_CODE_DECLARE(enc) \
int32_t cpt_code_##enc##_to_utf32be(uint8_t *dst, uint32_t *dst_size, const uint8_t *src, \
uint32_t src_size, uint32_t flags); \
int32_t cpt_code_utf32be_to_##enc(uint8_t *dst, uint32_t *dst_size, const uint8_t *src, \
uint32_t src_size, uint32_t flags);
CPT_CODE_DECLARE(utf8)
CPT_CODE_DECLARE(utf8bom)
CPT_CODE_DECLARE(utf16le)
CPT_CODE_DECLARE(utf16be)
CPT_CODE_DECLARE(utf32le)
#undef CPT_CODE_DECLARE

/* 符号化の番号(cpt_code_bound と cpt_code_detect が使う)。 */
enum {
CODE_UTF8 = 0,
CODE_UTF8_BOM = 1,
CODE_UTF16LE = 2,
CODE_UTF16BE = 3,
CODE_UTF32LE = 4,
CODE_UTF32BE = 5
};

/* from から to へ変換した出力が(flags によらず)必ず収まる byte 数。BOM 分を含む。片方が UTF-32BE の
* 変換用だが、どの組み合わせでも使える。表せない場合や符号化が不正な場合は 0xFFFFFFFF。 */
uint32_t cpt_code_bound(uint32_t src_size, uint32_t from, uint32_t to);

/* 先頭の BOM から符号化を判別する。BOM がなければ CODE_UTF8 を返す。bom_size(NULL 可)には BOM の長さ
* (なければ 0)を返す。判別できるのは CODE_UTF8_BOM、CODE_UTF16LE/BE、CODE_UTF32LE/BE。
* FF FE 00 00 は UTF-16LE の BOM と U+0000 でも解釈できるが UTF-32LE とする。src が NULL(src_size > 0)なら負の値。
* 判別した符号化の関数に src + *bom_size を渡す(utf8bom は BOM ごと渡してよい)。 */
int32_t cpt_code_detect(const uint8_t *src, uint32_t src_size, uint32_t *bom_size);
#endif
35 changes: 35 additions & 0 deletions foundation/lib/code/build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Build the character code conversion library as a Foundation WAL."""
import argparse
from pathlib import Path
import subprocess
import sys

HERE = Path(__file__).resolve().parent
ROOT = HERE.parents[2]


def build(out, clang=None, optimization="-O2"):
out = Path(out).resolve()
out.mkdir(parents=True, exist_ok=True)
command = [sys.executable, str(ROOT / "tools/llvm-src32/driver.py"),
str(HERE / "code.c"),
"-I", str(ROOT / "foundation/include"), optimization,
"--wal", "--image-id", "code", "--abi-version", "1.0",
"--exports", str(HERE / "code.exports"), "-o", str(out / "libcode.wal")]
if clang:
command += ["--clang", clang]
subprocess.run(command, check=True)
return out / "libcode.wal"


def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("out", type=Path)
parser.add_argument("--clang")
parser.add_argument("--optimization", choices=("-O0", "-O1", "-O2"), default="-O2")
args = parser.parse_args()
print(build(args.out, args.clang, args.optimization))


if __name__ == "__main__":
main()
Loading
Loading