vinix

Table of Contents

where filter

前回やった配列関連のメソッドだけど、どんな風に実装されてるの? 興味本位で 確認してみたい。遡上に載せるのは、簡単なコード。

sakae@lu:t$ cat mytest.v
fn main(){
        mylist := [1,2,3,4,5]
        myfil := mylist.filter(it % 2 == 1)
        println(myfil)
}

v help build-c したら、おあつらえなオプションが有ったので使ってみた。

sakae@lu:t$ v -dump-c-flags - -g mytest.v
-fwrapv
-g
-o "/tmp/t/mytest"
-D GC_THREADS=1
-D GC_BUILTIN_ATOMIC=1
-I "/var/my/srcs/v/thirdparty/libgc/include"
"/tmp/v_1000/mytest.01JPH8ZN2QW4A0AFVZYN2T09CP.tmp.c"
-std=gnu99
-D_DEFAULT_SOURCE
-bt25
-rdynamic
"/var/my/srcs/v/thirdparty/tcc/lib/libgc.a"
-ldl
-lpthread

知らないのが出てきたんで、即確認。

-bt N
    Display N callers in stack traces. This is useful with -g or -b.

Tiny C Compiler Reference Documentation

sakae@lu:t$ wc /tmp/v_1000/mytest.01JPH8ZN2QW4A0AFVZYN2T09CP.tmp.c
 11704  30752 417027 /tmp/v_1000/mytest.01JPH8ZN2QW4A0AFVZYN2T09CP.tmp.c

数行なコードが1万行を越える行数に展開されるって、驚くポイントですよ。

#line 1 "../../../../../../tmp/t/mytest.v"
void main__main(void) {

#line 2 "../../../../../../tmp/t/mytest.v"
        Array_int mylist = new_array_from_c_array_noscan(5, 5, sizeof(int), _MOV((int[5]){1, 2, 3, 4, 5}));
        Array_int _t1 = {0};
        Array_int _t1_orig = mylist;
        int _t1_len = _t1_orig.len;
        _t1 = __new_array_noscan(0, _t1_len, sizeof(int));

        for (int _t2 = 0; _t2 < _t1_len; ++_t2) {
                int it = ((int*) _t1_orig.data)[_t2];
                if ((int)(it % 2) == 1) {
                        array_push_noscan((array*)&_t1, &it);
                }
        }
        #line 3 "../../../../../../tmp/t/mytest.v"
        Array_int myfil =_t1;

#line 4 "../../../../../../tmp/t/mytest.v"
        println(Array_int_str(myfil));
}

デバッグに便利な様に行番号を付いてる。親切ですネェ。で、元コードに有った filterは影を潜めてすっかり解けてしまっているな。forで配列をスキャンしてるのは 読み取れるけど。まあ、配列に寄生して何かやるってのがメソッドの得意技だから、 自然な成行ではあるのですが。。

このあたり、純粋オブジェクト指向のrubyでは、どうなっているんだろう? 名著 RHG(Ruby Hacking Guide)を所有してる方の解説をキボンヌ。

map

今度はmapで確認。プロダクト版ね。

sakae@lu:t$ v -dump-c-flags - -prod mytest.v
-fwrapv
-O3
-flto
-DNDEBUG
"/home/sakae/.vmodules/.cache/2f/2f684be88af0f9e272b6028424eca5bd.module.builtin.o"
-o '/home/sakae/t/mytest'
-D GC_THREADS=1
-D GC_BUILTIN_ATOMIC=1
-I "/var/my/srcs/v/thirdparty/libgc/include"
"/tmp/v_1000/mytest.01JPJZFPVP8G486HZGYR2KF5DV.tmp.c"
-std=gnu99
-D_DEFAULT_SOURCE
-ldl
-lpthread
-lm

残念ながらC語のソースは直ぐに削除されちゃって閲覧不能だった。しょうがないので -gを 付けて永続化?したよ。

#line 3 "../../../../../../home/sakae/t/mytest.v"
        Array_int mylist = new_array_from_c_array_noscan(4, 4, sizeof(int), _MO\
V((int[4]){2, 3, 4, 5}));
        Array_string _t1 = {0};
        Array_int _t1_orig = mylist;
        int _t1_len = _t1_orig.len;
        _t1 = __new_array(0, _t1_len, sizeof(string));

        for (int _t3 = 0; _t3 < _t1_len; ++_t3) {
                int it = ((int*) _t1_orig.data)[_t3];
                string _t2 = f64_str(math__sqrt(it));
                array_push((array*)&_t1, &_t2);
        }
        #line 4 "../../../../../../home/sakae/t/mytest.v"
        Array_string myfil =_t1;

こちらもmapは影も形も消え失せている。素直にforが有って、粛々とmylist.map(math.sqrt(it).str()) の結果を矯め込んでいる。一応、 実行結果をば。

sakae@lu:t$ ./mytest
['1.4142135623730951', '1.7320508075688772', '2.0', '2.23606797749979']

ケチな人には、たまにはルート3してよ(ひとなみに、おごれや)と言ってやりたいぞ。 今の人は、こういう語呂合わせで数値を覚えるって事しないのかな。

新人にお小遣いをあげて、どや良い先輩だろうとにんまりしてたら、野党やら身内 からも顰蹙を買った首相がいた(まだいるな)。TPOを弁えて、鳥貴族あたりで新人 歓迎のコンパでもやっとけば良かったのに。それってパワハラ兼アルハラと言われ かねないか。どっちにころんでも、大変だ脳。おつむが更にツルツルになりますよ。

complex

配列のメソッドは組み込みだったので、vlibになってる、それを見て億。例にする のは、前回やった複素数ね。

void main__main(void) {
#line 3 "../../../../../../tmp/t/mytest.v"
        math__complex__Complex z = math__complex__complex(3, 4);
#line 4 "../../../../../../tmp/t/mytest.v"
        f64 myth = math__complex__Complex_angle(z);
#line 5 "../../../../../../tmp/t/mytest.v"
        f64 myps = math__complex__Complex_abs(z);

インライン展開せずに、普通の関数呼出になってる。関数名が鬼のように長いな。 absを追跡してみる。

#line 30 "../../../../../../var/my/srcs/v/vlib/math/complex/complex.v"
f64 math__complex__Complex_abs(math__complex__Complex c) {
#line 31 "../../../../../../var/my/srcs/v/vlib/math/complex/complex.v"
        return math__hypot(c.re, c.im);
}

これの元ネタは

pub fn (c Complex) abs() f64 {
        return math.hypot(c.re, c.im)
}

だったから、機械的な置き換えで済むんだな。

builtin

前回のm8pをOpenBSDのvlang(V 0.4.5)で確認したら下記の様に見事にpanic

vm$ cat vmware | ./m8p
================ V panic ================
   module: builtin
 function: tos()
  message: tos(): nil string
     file: /usr/local/lib/vlang/vlib/builtin/string.v:105
   v hash: a03da95
=========================================

そんな事より衝撃を受けたのは、vlib/builtinが有った事。見事にスルーしてました。 オイラーの目は節穴です。気を取り直して、そこのREADMEを見ると

`builtin` is a module that is implicitly imported by every V program.

It implements the builtin V types `array`, `string`, `map`.

It also implements builtin functions like `println`, `eprintln`, `malloc`,
`panic`, `print_backtrace`.

The autogenerated documentation for `builtin` functions is lacking, so for these
functions, please refer to the
[official V documentation](https://github.com/vlang/v/blob/master/doc/docs.md).

配列のメソッドを改めて確認すると、こんな風に定義されてたぞ。

pub fn (a array) filter(predicate fn (voidptr) bool) array
pub fn (a array) map(callback fn (voidptr) voidptr) array

冒頭にpubが付いてて、皆に公開するね。引数は関数だからねってのは、いにしえから 有るLispの血筋を受け継いでいます。

ついでに配列の構造。

// array is a struct, used for denoting all array types in V.
// `.data` is a void pointer to the backing heap memory block,
// which avoids using generics and thus without generating extra
// code for every type.
pub struct array {
pub mut:
        data   voidptr
        offset int // in bytes (should be `usize`), to avoid copying data while making slices, unless it starts changing
        len    int // length of the array in elements.
        cap    int // capacity of the array in elements.
        flags  ArrayFlags
pub:
        element_size int // size in bytes of one element in the array.
}

これで、下記の {} の意味が氷解したよ(今頃かい)。

>>> mut c := []int {len: 5}
>>> c
[0, 0, 0, 0, 0]

orm

たまたま、こんな本を読んでいたんだ。 世界最凶のスパイウェア ペガサス 池上 彰さんが警鐘を鳴らしている、 「あなたのスマホは、大丈夫ですか?」って。

アンドロイドOSでは記録を余り残さない仕様みたいだけど、アプルのそれはタイムマシン を売りにしてるせいか、しっかり記録を残しているそうな。で、それにsqliteを使って いるとか。

vlangはwebのサポートもしっかり行なわれている。バックエンドのDBサポートも必須。 ormってライブラリィーにて提供されてた。ボラクルは論外だけど、mysql,pg,mssql,sqlite はサポートされてた。

sakae@lu:orm$ v test .
---- Testing... ----------------------------------------------------------------
 OK    [ 1/23] C:   425.6 ms, R:     3.516 ms /var/my/srcs/v/vlib/orm/orm_fk_test.v
 OK    [ 2/23] C:   689.6 ms, R:     4.434 ms /var/my/srcs/v/vlib/orm/orm_custom_operators_test.v
 :
 OK    [22/23] C:   527.1 ms, R:     3.054 ms /var/my/srcs/v/vlib/orm/orm_sum_type_insert_test.v
 OK    [23/23] C:   474.0 ms, R:     5.913 ms /var/my/srcs/v/vlib/orm/orm_test.v
--------------------------------------------------------------------------------
Summary for all V _test.v files: 23 passed, 23 total. Elapsed time: 4557 ms, on 3 parallel jobs. Comptime: 13166 ms. Runtime: 1
08 ms.

最初テストがことごとくフェイルした。例のいやがらせね。sudo apt install libsqlite3-dev これにて無事を確認した。はて、何に使おうか。

vinix

近頃のリナはC言語推しの保守派とrust推しの革新派のせめぎあいで政治論争に なってるらしい。rust派の人が辞任するとか。そんな事を尻目に、清くvlangで カーネルを作成しましょって中道派が、ひそかに活動してる。して、その名は

vlang / vinix

興味深いので参戦してみる。

makeすると、gccやらのユーザーランドのコンパイルが始まった。しゃらくさいので、 ISOを頂いてきて、実行してみた。

ISO

Limine ってのがgrubに代って採用されてる。シンプルなんだろうね。ISOを分解 して、どんな構成になってるか確認。

ob$ doas vnconfig vnd0 /var/SRC/vinix/vinix.iso
ob$ doas mount -t cd9660 /dev/vnd0c /mnt
ob$ tree /mnt
/mnt
|-- EFI
|   `-- BOOT
|       |-- BOOTIA32.EFI
|       `-- BOOTX64.EFI
|-- boot
|   |-- initramfs.tar
|   |-- limine-bios-cd.bin
|   |-- limine-bios.sys
|   |-- limine-uefi-cd.bin
|   |-- limine.conf
|   `-- vinix
`-- boot.catalog

limine.confはこんなの。カーネルガvinixで、モジュールはinitramfsとな。

timeout: 3

/Vinix
    protocol: limine
    kernel_path: boot():/boot/vinix
    module_path: boot():/boot/initramfs.tar

そのモジュール扱かいされる中身は

initramfs.tar

ob$ ls -l
total 12
lrwxr-xr-x  1 sakae  wheel    7 Mar 15 09:58 bin@ -> usr/bin
drwxr-xr-x  3 sakae  wheel  512 Mar 15 09:58 etc/
lrwxr-xr-x  1 sakae  wheel    7 Mar 15 09:58 lib@ -> usr/lib
lrwxr-xr-x  1 sakae  wheel    7 Mar 15 09:58 lib64@ -> usr/lib
drwxr-xr-x  2 sakae  wheel  512 Mar 15 09:58 root/
drwxr-xr-x  2 sakae  wheel  512 Mar 15 09:58 run/
lrwxr-xr-x  1 sakae  wheel    7 Mar 15 09:58 sbin@ -> usr/bin
drwxr-xr-x  2 sakae  wheel  512 Mar 15 09:58 tmp/
drwxr-xr-x  7 sakae  wheel  512 Mar 15 11:03 usr/
drwxr-xr-x  3 sakae  wheel  512 Mar 15 09:58 var/

リナでISOの中身を確認するなら、こうするのかな。OpenBSDより、ちょっと楽天か。

sakae@lu:vinix$ sudo mount -o loop /var/my/srcs/vinix/vinix.iso /mnt
mount: /mnt: WARNING: source write-protected, mounted read-only.
sakae@lu:vinix$ ls /mnt
EFI/  boot/  boot.catalog

run-kvm

GNUMakefileを見ると、kvmで起動できる様にターゲットが設定されてた。但しメモリー8G で4cpuって設定だったので、軽くした。

残念ながらVGAな画面なので、起動時からのログは提示できない。bashが起動して くると、com1にリダイレクトが可能になるので、ホスト側に情報を流せるようになる。

root@vinx [ ~ ] # ls -l /dev > /dev/com1            ;; in gestOS
sakae@lu:vinix$ make run-kvm                        ;; on Lubuntu
qemu-system-x86_64 -enable-kvm -cpu host -M q35,smm=off -m 4G -cdrom vinix.iso -serial stdio -smp 2
crw-r--r-- 1 root root 0,  9 Mar 19 06:52 com1
crw-r--r-- 1 root root 0,  8 Mar 19 06:52 console
crw-rw-rw- 1 root root 0,  7 Mar 19 06:52 fb0
crw-rw-rw- 1 root root 0,  5 Mar 19 06:52 full
crw-r--r-- 1 root root 0, 10 Mar 19 06:52 mouse
crw-rw-rw- 1 root root 0,  3 Mar 19 06:52 null
crw-rw-rw- 1 root root 0,  6 Mar 19 06:52 urandom
crw-rw-rw- 1 root root 0,  4 Mar 19 06:52 zero

gcc -v > /dev/com1 2>&1

Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-vinix-mlibc/14.2.0/lto-wrapper
Target: x86_64-vinix-mlibc
Configured with: /base_dir/sources/gcc/configure ...
  :
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 14.2.0 (GCC)

ちゃんとハロワがコンパイル&実行できたから、カーネルとしては、ちゃんと動作 してるっぽい。

vlangで記述されたユーザーランドも用意されてた。下記はそのうちのlscpu

Architecture:     x86_64
CPU op-mode(s):   32-bit, 64-bit
Address sizes:    39 bits physical, 48 bits linear
Byte Order:       Little Endian
CPU Count:        2
Vendor ID:        GenuineIntel
Model name:       Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz
CPU family:       6
Model number:     78
Stepping:         3
Flags:            aes, apic, avx, ... ssse3, tsc, vme, vmx, x2apic, xsave

see source

上で出てきた奴から。

sakae@lu:vinix$ ls util-vinix/lscpu/
cpu_x64.v  main.v  v.mod

main.v

module main

import os
import cpu

fn main() {
        mut idx := 1
        for idx < os.args.len {
                match os.args[idx] {
                        '--help' {
         :
        info := cpu.get_cpu_info() or {
                println('Could not fetch CPU information')
                exit(1)
        }
        info.print()

mainは何の変哲もないな。ああ、matchなんてのがポートされてるのか。途中で乱入 してるんで、良く知らないな。感覚で見ております。

module cpu
  :
pub fn get_cpu_info() ?CPUInfo {
        // Fetch vendor.
        mut str0 := &RawVendorID{}
        _, _, str0.ebx, str0.ecx, str0.edx = cpuid(0, 0)
        vendor_id := unsafe { cstring_to_vstring(charptr(&str0.ebx)) }

        // Fetch model, stepping and family.
        _, a1, _, c1, d1 := cpuid(1, 0)
	:
        return CPUInfo{
                address_sizes: [physical_size, linear_size]
                is_little_endian: true
                cpu_count: core_count
                vendor_id: vendor_id
                model_name: model_name
                cpu_family: cpu_family
                model_number: model_number
                stepping: stepping_id
                flags: flags
        }

アセンブラの世界から情報を引いてきて、最後は、構造体にして返却ってやってる。 これがcpuモジュールの定義とな。

これならlubuntu側でも動くんじゃなかろうか? 試してみる。モジュールが別々に 定義されてる場合、それが存在するdirを指定してコンパイルすればいいのか。そんな 事はエラー駆動でhelp buildして知見を得たぞ。

sakae@lu:util-vinix$ v lscpu
sakae@lu:util-vinix$ ls -l lscpu/
合計 468
-rw-rw-r-- 1 sakae sakae   9191  3月 16 06:08 cpu_x64.v
-rwxrwxr-x 1 sakae sakae 457032  3月 20 06:33 lscpu*
-rw-rw-r-- 1 sakae sakae    651  3月 16 06:08 main.v
-rw-rw-r-- 1 sakae sakae    130  3月 16 06:08 v.mod
sakae@lu:util-vinix$ lscpu/lscpu
Architecture:     x86_64
CPU op-mode(s):   32-bit, 64-bit
  :

もう一発、軽いfetchが有ったので試した。

sakae@lu:util-vinix$ v fetch
sakae@lu:util-vinix$ fetch/fetch
 __          __
 \ \        / //  sakae@lu
  \ \      / //   OS:     Linux x86_64 #57-Ubuntu SMP PREEMPT_DYNAMIC Wed Feb 12 23:42:21 UTC 2025
   \ \    / //    KERNEL: Linux
    \ \  / //     SHELL:  /bin/bash
     \ \/ //
      \/_//

 __          __
 \ \        / //  root@vinix
  \ \      / //   OS:
   \ \    / //    KERNEL:
    \ \  / //     SHELL:  /bin/bash
     \ \/ //
      \/_//

下側のやつはゲストOSであるvinuxからの転送結果。まだ間に合ってない項目が有る んだね。頑張って実装して下さい。

init

大事なbashを起動する奴。これなくしては、ユーザーは手も足も出ない。カーネルの 起動の最後に、これが呼び出される。

fn main() {
        println('Vinix Init started')

        os.setenv('HOME', '/root', true)
        os.setenv('TERM', 'linux', true)
        os.setenv('PATH', '/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin', true)
        os.setenv('USER', 'root', true)
	:
        os.chdir('/root') or { panic('Could not move to root') }
        :
        for {
                os.system("exec -a '-bash' bash --login")
        }

儀式として環境変数をセットしてから、所定の場所に移動。そしてbashを起動する。 簡易ではあるが、よく動きが分かるな。

How to make ISO

途中で諦めてしまったISO作成工程。懺悔して作成手順を確認しとけ。 最終的にはbuild-support/makeiso.shでISOが作成されるんだけど、その為の内容物を 準備しておかねばならない。

それには、jinxと言う現場監督が招聘される。recipesの中に格納されてる設計図を 頼りにソースを取り寄せ、sourcesの下を作業場所としてコンパイルしてくって仕組み のようだ。途中まで作成した残骸が3.6Gもsourcesの下に残っていたぞ。full-build したら、一体どこまで肥大化するやら? 怖くて継続する有機が有りませんです。

次回は、いよいよ本丸のkernelかな。

README

ウェイリー版 源氏物語を読んだ。100分de名著の方ね。

紫式部が書いた有名な物語。古語なんで、難しくて読めんと言う事で、与謝野晶子 さんが現代風に翻訳。

イギリスの大英博物館に勤務してたウェイリー氏も独自に英訳。これで世界に 広がり絶賛されるようになった。更にその英訳を元に、日本語に翻訳された方も おられる。これが読みやすいと評判らしい。

原文では几帳となってる所をcurtainと翻訳した。これを日本語に再翻訳する時 カーテンとした。几帳とはしなかったんだ。翻訳には2種あるという。

同化翻訳、翻訳先の文化に同化させるって方法。異化翻訳は、異質のまま翻訳して、 注を入れるって方法。 AIの翻訳は、どっちの方針を採用してんかな? とか考えちゃったぞ。

紫式部日記なんてのも勧められていた。同業者の枕草子を書いた清少納言を こっぴどく、こきおろしてるのには笑ってしまった。

光源氏を、シャイニング・プリンス(光る君)と訳した。大河ドラマの方はパクったんだな。


This year's Index

Home