kubo39's blog

ただの雑記です。

LDCでwasmのmultivalue拡張できないか軽く試す

LDCでWebAssemblyのmultivalue拡張を試したい。

コンパイラはLDC1.25.1でまず試してみる。

以下のようなコードを書いてみる。

extern (C):
nothrow:
@nogc:

struct TwoI32
{
    int aa;
    int bb;
}

export TwoI32 return_two_i32()
{
    return TwoI32(1, 2);
}
$ ldc2 -mtriple=wasm32-unknown-unknown -mattr=+multivalue -betterC -fvisibility=hidden --frame-pointer=none -L=-no-entry multivalue.d

生成したwasmをwat形式にするとこのようになり、+multivalueが効いていないことがわかる。

(module
  (type (;0;) (func (param i32)))
  (func $return_two_i32 (type 0) (param i32)
    (local i32 i32)
    i32.const 2
    local.set 1
    i32.const 1
    local.set 2
    local.get 0
    local.get 2
    i32.store
    local.get 0
    local.get 1
    i32.store offset=4
    return)
  (table (;0;) 1 1 funcref)
  (memory (;0;) 2)
  (global (;0;) (mut i32) (i32.const 66560))
  (export "memory" (memory 0))
  (export "return_two_i32" (func $return_two_i32)))

どのような流れでコンパイルされているかを知るために、verboseフラグをたててみる。 LDCで詳しくみたい場合は-vvをつける。

$ ldc2 -mtriple=wasm32-unknown-unknown -mattr=+multivalue -betterC -fvisibility=hidden --frame-pointer=none -L=-no-entry -vv multivalue.d
Targeting 'wasm32-unknown-unknown' (CPU 'generic' with features '+multivalue')
WARNING: Unknown ABI, guessing...
Building type: real
Building type: uint
Building type: int
CodeGenerator::emit(multivalue)
* resetting 3848 Dsymbols
* *** Initializing D runtime declarations ***
* * building runtime module
* Import::codegen for multivalue.object
* StructDeclaration::codegen: 'multivalue.TwoI32'
* * Resolving struct type: TwoI32 (multivalue.d(5))
* * * Building type: TwoI32
* * * * Building struct type multivalue.TwoI32 @ multivalue.d(5)
* * * * * Field priority for aa: 1
* * * * * Field priority for bb: 1
* * * * * final struct type: %multivalue.TwoI32 = type { i32, i32 }
* * VarDeclaration::codegen(): 'multivalue.TwoI32.aa'
* * * DtoResolveVariable(multivalue.TwoI32.aa)
* * VarDeclaration::codegen(): 'multivalue.TwoI32.bb'
* * * DtoResolveVariable(multivalue.TwoI32.bb)
* DtoDefineFunction(multivalue.return_two_i32): multivalue.d(11)
* * DtoFunctionType(nothrow @nogc extern (C) TwoI32())
* * * Building type: TwoI32*
* * * Building type: void
* * * Final function type: void (%multivalue.TwoI32*)
* * DtoResolveFunction(multivalue.return_two_i32): multivalue.d(11)
* * DtoDeclareFunction(multivalue.return_two_i32): multivalue.d(11)
* * * DtoFunctionType(nothrow @nogc extern (C) TwoI32())
* * * func = declare void @return_two_i32(%multivalue.TwoI32*)

DtoDefineFunctionまわりが怪しそう。

  • Final function type: void (%multivalue.TwoI32*)
    • irFty.ret->ltypeがvoidになってそう
    • ここはTwoI32がきてほしい

そろそろコードをみてみる。

とりあえず上のほうをみると、TargetABIというアーキテクチャごとのABIにかんする情報をもってそうなものがある。

llvm::FunctionType *DtoFunctionType(Type *type, IrFuncTy &irFty, Type *thistype,
                                    Type *nesttype, FuncDeclaration *fd) {
  IF_LOG Logger::println("DtoFunctionType(%s)", type->toChars());
  LOG_SCOPE

  // sanity check
  assert(type->ty == Tfunction);
  TypeFunction *f = static_cast<TypeFunction *>(type);
  assert(f->next && "Encountered function type with invalid return type; "
                    "trying to codegen function ignored by the frontend?");

  // Return cached type if available
  if (irFty.funcType) {
    return irFty.funcType;
  }

  TargetABI *abi = fd && DtoIsIntrinsic(fd) ? TargetABI::getIntrinsic() : gABI;
(...)

LDCのコードをみるとwasm32ではABI判定がされないのでUnknownTargetABIになる。

TargetABI *TargetABI::getTarget() {
  switch (global.params.targetTriple->getArch()) {
  case llvm::Triple::x86:
    return getX86TargetABI();
  case llvm::Triple::x86_64:
    if (global.params.targetTriple->isOSWindows()) {
      return getWin64TargetABI();
    } else {
      return getX86_64TargetABI();
    }
  case llvm::Triple::mips:
  case llvm::Triple::mipsel:
  case llvm::Triple::mips64:
  case llvm::Triple::mips64el:
    return getMIPS64TargetABI(global.params.is64bit);
  case llvm::Triple::ppc:
  case llvm::Triple::ppc64:
    return getPPCTargetABI(global.params.targetTriple->isArch64Bit());
  case llvm::Triple::ppc64le:
    return getPPC64LETargetABI();
  case llvm::Triple::aarch64:
  case llvm::Triple::aarch64_be:
    return getAArch64TargetABI();
  case llvm::Triple::arm:
  case llvm::Triple::armeb:
  case llvm::Triple::thumb:
  case llvm::Triple::thumbeb:
    return getArmTargetABI();
  default:
    Logger::cout() << "WARNING: Unknown ABI, guessing...\n";
    return new UnknownTargetABI;
  }
}

UnknownTargetABIは以下のような構造になっている。

// Some reasonable defaults for when we don't know what ABI to use.
struct UnknownTargetABI : TargetABI {
  bool returnInArg(TypeFunction *tf, bool) override {
    if (tf->isref()) {
      return false;
    }

    // Return structs and static arrays on the stack. The latter is needed
    // because otherwise LLVM tries to actually return the array in a number
    // of physical registers, which leads, depending on the target, to
    // either horrendous codegen or backend crashes.
    Type *rt = tf->next->toBasetype();
    return passByVal(tf, rt);
  }

  bool passByVal(TypeFunction *, Type *t) override {
    return DtoIsInMemoryOnly(t);
  }

  void rewriteFunctionType(IrFuncTy &) override {
    // why?
  }
};
bool DtoIsInMemoryOnly(Type *type) {
  Type *typ = type->toBasetype();
  TY t = typ->ty;
  return (t == Tstruct || t == Tsarray);
}

このままだとこういうLLVM IRを吐く。

ldc2 -mtriple=wasm32-unknown-unknown -mattr=+multivalue -betterC -fvisibility=hidden --frame-pointer=none --output-ll -L=-no-entry multivalue.d
(...)
; [#uses = 0]
define void @return_two_i32(%multivalue.TwoI32* noalias sret align 4 %.sret_arg) #0 {
  %1 = getelementptr inbounds %multivalue.TwoI32, %multivalue.TwoI32* %.sret_arg, i32 0, i32 0 ; [#uses = 1, type = i32*]
  store i32 1, i32* %1, align 4
  %2 = getelementptr inbounds %multivalue.TwoI32, %multivalue.TwoI32* %.sret_arg, i32 0, i32 1 ; [#uses = 1, type = i32*]
  store i32 2, i32* %2, align 4
  ret void
}

attributes #0 = { "frame-pointer"="none" "target-cpu"="generic" "target-features"="+multivalue" }

というわけで、これを修正する必要がある。

継承関係とかみるとこのへんが呼ばれるらしい。

  • bool returnInArg(TypeFunction *tf, bool)
  • bool passByVal(TypeFunction , Type t)
  • void rewriteFunctionType(IrFuncTy &)

関数の返り値がvoidになっているのがまずおかしい。 強引に修正するなら、とりあえず以下のようにやるとTwoI32型の返り値にはなる。

bool returnInArg(TypeFunction *tf, bool) override {
    return false;
}

これで生成されるLLVM IRはTwoI32を返すようにはなる。

; ModuleID = 'multivalue.d'
source_filename = "multivalue.d"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32-unknown-unknown"

%multivalue.TwoI32 = type { i32, i32 }

; [#uses = 0]
define %multivalue.TwoI32 @return_two_i32() #0 {
  %.structliteral = alloca %multivalue.TwoI32, align 4 ; [#uses = 3, size/byte = 8]
  %1 = getelementptr inbounds %multivalue.TwoI32, %multivalue.TwoI32* %.structliteral, i32 0, i32 0 ; [#uses = 1, type = i32*]
  store i32 1, i32* %1, align 4
  %2 = getelementptr inbounds %multivalue.TwoI32, %multivalue.TwoI32* %.structliteral, i32 0, i32 1 ; [#uses = 1, type = i32*]
  store i32 2, i32* %2, align 4
  %3 = load %multivalue.TwoI32, %multivalue.TwoI32* %.structliteral, align 4 ; [#uses = 1]
  ret %multivalue.TwoI32 %3
}

attributes #0 = { "frame-pointer"="none" "target-cpu"="generic" "target-features"="+multivalue" }

!llvm.ident = !{!0}

!0 = !{!"ldc version 1.26.0-git-e6deb6c-dirty"}

ただし今度は生成されるwasmがinvalidになってしまう。

$ wasm2wat multivalue.wasm
000000e: error: invalid function result count 2, only 1 bytes left in section

ひとまずここまで。続くかもしれないし続かないかもしれない。

mysql-nativeで一度に受信できるパケットサイズは16777210バイト

コードを読んでもとくにサイズ上限が規定されていなかったので実験した。

  • 環境(mysql-nativeのバージョン)
$ cat dub.selections.json| grep mysql-native
                "mysql-native": "3.0.0",

以下のようにmax_allowed_packetや受信サイズを調整して試した結果、max_allowed_packetのサイズにかかわらず16777210バイトが上限になるようだ。 というわけでいくらmax allowed packet sizeを16MBより大きくしても効果がないので注意する必要がある。

import mysql.connection;
import mysql.commands;

import std.array : array;
import std.range;

void main()
{
    auto conn = new Connection("host=127.0.0.1;port=3307;db=testdb;user=testuser;pwd=testpassword");
    scope(exit) conn.close();
    auto row = conn.queryRow("SELECT REPEAT('A', 16777210)");

    assert(row[0].get!string == 'A'.repeat.take(16_777_210).array);
    //
    // max_allowed_packet = 16MB,24MB
    //
    // OK: 15_728_640 (15MB)
    // OK: 16_777_210 (16MB - 6)
    // NG: 16_777_211
    // NG: 16_777_216 (16MB)
    //
    // 1MB = 1024 * 1024 = 1048576
    // 15MB = 15 * 1024 * 1024 = 15 * 1048576 = 15_728_640
    // 16MB = 16 * 1024 * 1024 = 16 * 1048576 = 16_777_216
}

1バイトでも超えると以下のようなエラーになる。

$ dub run
Performing "debug" build using /home/kubo39/dlang/dmd-2.094.0/linux/bin64/dmd for x86_64.
taggedalgebraic 0.11.18: target for configuration "library" is up to date.
eventcore 0.9.9: target for configuration "epoll" is up to date.
stdx-allocator 2.77.5: target for configuration "library" is up to date.
vibe-core 1.10.3: target for configuration "epoll" is up to date.
mysql-native 3.0.0: target for configuration "library" is up to date.
large-packet ~master: building configuration "application"...
Linking...
To force a rebuild of up-to-date targets, run again with --force.
Running ./large-packet
core.exception.AssertError@../../../../.dub/packages/mysql-native-3.0.0/mysql-native/source/mysql/protocol/packet_helpers.d(366): Assertion failure
----------------
??:? _d_assertp [0x56231b799d9d]
../../../../.dub/packages/mysql-native-3.0.0/mysql-native/source/mysql/protocol/packet_helpers.d:366 pure nothrow @nogc @safe ubyte[] mysql.protocol.packet_helpers.consume!().consume(ref ubyte[], ulong) [0x56231b6ac06d]
../../../../.dub/packages/mysql-native-3.0.0/mysql-native/source/mysql/protocol/packet_helpers.d:723 mysql.protocol.extra_types.SQLValue mysql.protocol.packet_helpers.consumeIfComplete!().consumeIfComplete(ref ubyte[], mysql.protocol.constants.SQLType, bool, bool, ushort) [0x56231b6bfb61]
../../../../.dub/packages/mysql-native-3.0.0/mysql-native/source/mysql/protocol/comms.d:673 void mysql.protocol.comms.ctorRow(mysql.connection.Connection, ref ubyte[], mysql.protocol.packets.ResultSetHeaders, bool, out std.variant.VariantN!(32uL).VariantN[], out bool[], out immutable(char)[][]) [0x56231b6bd57b]
../../../../.dub/packages/mysql-native-3.0.0/mysql-native/source/mysql/result.d:55 ref mysql.result.Row mysql.result.Row.__ctor(mysql.connection.Connection, ref ubyte[], mysql.protocol.packets.ResultSetHeaders, bool) [0x56231b6af8b1]
../../../../.dub/packages/mysql-native-3.0.0/mysql-native/source/mysql/protocol/comms.d:886 mysql.result.Row mysql.protocol.comms.getNextRow(mysql.connection.Connection) [0x56231b6be519]
../../../../.dub/packages/mysql-native-3.0.0/mysql-native/source/mysql/result.d:278 void mysql.result.ResultRange.popFront() [0x56231b6aff8f]
../../../../.dub/packages/mysql-native-3.0.0/mysql-native/source/mysql/result.d:233 ref mysql.result.ResultRange mysql.result.ResultRange.__ctor(mysql.connection.Connection, mysql.protocol.packets.ResultSetHeaders, immutable(char)[][]) [0x56231b6afe1c]
../../../../.dub/packages/mysql-native-3.0.0/mysql-native/source/mysql/commands.d:384 mysql.result.ResultRange mysql.commands.queryImpl(mysql.commands.ColumnSpecialization[], mysql.connection.Connection, mysql.protocol.comms.ExecQueryImplInfo) [0x56231b6b0884]
../../../../.dub/packages/mysql-native-3.0.0/mysql-native/source/mysql/commands.d:506 std.typecons.Nullable!(mysql.result.Row).Nullable mysql.commands.queryRowImpl(mysql.commands.ColumnSpecialization[], mysql.connection.Connection, mysql.protocol.comms.ExecQueryImplInfo) [0x56231b69e340]
../../../../.dub/packages/mysql-native-3.0.0/mysql-native/source/mysql/commands.d:453 std.typecons.Nullable!(mysql.result.Row).Nullable mysql.commands.queryRow(mysql.connection.Connection, const(char[]), mysql.commands.ColumnSpecialization[]) [0x56231b69e2cb]
source/app.d:11 _Dmain [0x56231b69d17c]
Program exited with code 1

mysql-nativeでbulk insertをする

最近はMySQLを触っている中で、なにかしらメモを残しておくかという気分になった。

mysql-nativeにはbulk insert用のサポートがあるわけではないが、メタプログラミングコンパイル時にクエリをある程度生成できたりすると便利だなあと思うなどした。

  • 環境
$ dmd --version
DMD64 D Compiler v2.094.0
$ cat dub.selections.json | grep mysql-native
                "mysql-native": "3.0.0",
  • コード (修正版)
import mysql.commands;
import mysql.connection;
import mysql.pool;
import std.algorithm : map;
import std.array :array;
import std.format : format;
import std.range : iota, repeat;
import std.stdio : writeln;
import std.string : join;

// vibe-coreのコネクションプールを使う
version = Have_vibe_core;

__gshared MySQLPool pool;

shared static this()
{
    string connStr = "host=localhost;port=3307;user=testuser;pwd=testpassword;db=testdb";
    // コネクションプールを生成
    pool = new MySQLPool(connStr);

    // デフォルトの最大並列接続数はuint.max
    // ここでは10に設定しておく
    pool.maxConcurrency(10);
}

auto bulkInsert(S, string table, S[] objects)(Connection conn)
{
    enum stmt = "INSERT INTO %s (%s) VALUES ".format(table, [__traits(allMembers, S)].join(","));
    enum rows = "(%s)".format("?".repeat(S.tupleof.length).join(",")).repeat(objects.length).join(",");
    mixin(`return conn.exec("` ~ stmt ~ rows ~ `",` ~
          objects.length.iota.map!(i => "objects[%d].tupleof".format(i)).join(",") ~
          ");");
}

void main()
{
    // プールからひとつコネクションをとってくる
    auto conn = pool.lockConnection();
    scope(exit) conn.close();

    // 一時テーブルを作成
    conn.exec("CREATE TEMPORARY TABLE `payment` (
        `customer_id` int not null,
        `amount` int not null,
        `account_name` text
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    struct Payment
    {
        int customer_id;
        int amount;
        string account_name;
    }
    const payments = [
        Payment(1, 2, null),
        Payment(3, 4, "foo"),
        Payment(5, 6, null)];

    const affectedRows = conn.bulkInsert!(Payment, "payment", payments);

    enum select = "SELECT %s FROM payment".format([__traits(allMembers, Payment)].join(","));
    const result = conn.query(select)
        .map!(row => Payment(row[0].get!int, row[1].get!int, row[2].get!string))
        .array;

    writeln(result);
}

テストするのに最近はdockerを使っている。これは便利。 このくらいのクエリなら意味はないけど、bulk insertするときは max_allowed_packet を大きめに指定しておく。

$ docker run --rm -d -p 127.0.0.1:3307:3306 --name=mariadb10 -e MYSQL_DATABASE=testdb -e MYSQL_USER=testuser -e MYSQL_PASSWORD=testpassword -e MYSQL_ALLOW_EMPTY_PASSWORD=yes mariadb:10.1 --max_allowed_packet=16MB
  • 実行結果
$ dub run -q
[const(Payment)(1, 2, ""), const(Payment)(3, 4, "foo"), const(Payment)(5, 6, "")]

Prologを使ってD言語のtraitsを表現してみる

D言語traitsのうちいくつか簡単に実装できそうなものを表現してみた。 といっても非常に不完全な状態であるが。

SWI-Prologを使ったが、他の実装でもおそらく問題ない。

$ swipl --version
SWI-Prolog version 7.6.4 for amd64

ソースコードは以下のような感じになっている。isScalarとかも実装したかったのだけれど、なかなか綺麗な感じにならなかった。

%  If the arguments are all either types that are arithmetic types.
traits(isArithmetic, Type1, Type2) :-
    traits(isArithmetic, Type1), traits(isArithmetic, Type2).

traits(isArithmetic, Type) :-
    traits(isIntegral, Type);
    traits(isFloating, Type).

%  Works like isArithmetic, except it's for integral types.
traits(isIntegral, byte).
traits(isIntegral, short).
traits(isIntegral, int).
traits(isIntegral, long).
traits(isIntegral, cent).
traits(isIntegral, ptrdiff_t).
traits(isIntegral, Type) :- traits(isUnsigned, Type).

%  Works like isArithmetic, except it's for floating types.
traits(isFloating, float).
traits(isFloating, cfloat).
traits(isFloating, ifloat).
traits(isFloating, double).
traits(isFloating, cduble).
traits(isFloating, idouble).
traits(isFloating, real).
traits(isFloating, creal).
traits(isFloating, ireal).

%  Works like isArithmetic, except it's for unsigned types.
traits(isUnsigned, bool).
traits(isUnsigned, char).
traits(isUnsigned, wchar).
traits(isUnsigned, dchar).
traits(isUnsigned, ubyte).
traits(isUnsigned, ushort).
traits(isUnsigned, uint).
traits(isUnsigned, ulong).
traits(isUnsigned, ucent).
traits(isUnsigned, size_t).

%  If the type's default initializer is all zero.
traits(isZeroInit, void).
traits(isZeroInit, byte).
traits(isZeroInit, short).
traits(isZeroInit, int).
traits(isZeroInit, long).
traits(isZeroInit, cent).
traits(isZeroInit, ptrdiff_t).
traits(isZeroInit, bool).
traits(isZeroInit, ubyte).
traits(isZeroInit, ushort).
traits(isZeroInit, uint).
traits(isZeroInit, ulong).
traits(isZeroInit, ucent).
traits(isZeroInit, size_t).

REPLで問い合わせてみる。

$ swipl traits.pl 
(...)
?- traits(isArithmetic, int).
true .

?- traits(isArithmetic, int, float).
true .

?- traits(isUnsigned, short).
false.

今の段階ではとくにできることがおもいつかないが、もうちょっと別のtraitsの表現も試してなんかおもしろいことができないか試していきたい。

D言語の名前修飾はUnicode識別子をエンコードしない

D言語はIdentifierとしてUnicode Codepointを使うことができる。

従って、以下のようなコードを書くことができる。

void ほげ() {}  // これは合法

上記の関数が名前修飾されると、以下のように修飾された名前の中にUnicode識別子が登場する。

$ dmd -c hoge.d
$ nm hoge.o
0000000000000000 t 
0000000000000000 R _D4hoge12__ModuleInfoZ
0000000000000000 W _D4hoge6ほげFZv
                 U __start_minfo
                 U __stop_minfo
                 U _d_dso_registry

これは名前修飾のABI仕様上合法である。

実際にUnicode識別子をIdentifierとして用いなければ問題とはならないが、例えばこういった文字集合を含んだシンボル名を解釈しないプラットフォームなどがある場合には問題が起きる。 (EDIt: 正確にいうと、D言語ソースコードUTF-8で書かれていると仕様で規定されているので、UTF-8エンコードされたシンボルが常に入っていることになると思われる。)

SwiftやRustの新しい名前修飾(v0)ではpunycode方式でUnicode識別子をエンコーディング(実際はRustの場合はさらに別のエンコーディング方式も用いている)することで対応する文字集合の範囲を狭めている。

D言語でRISC-Vシミュレータ上でハローワールドする

D言語でもRISC-Vやりたいんだが…? ← できた。そういう話です。

準備

LDCのバージョンは以下のもの。自前ビルドしてる人はRISC-V向けにしてることと、RISC-VはLLVM 9.0.0以降で公式にサポートされていることに注意されたい。

$ ldc2 --version | head -2
LDC - the LLVM D compiler (1.21.0):
  based on DMD v2.091.1 and LLVM 10.0.0
$ ldc2 --version | grep RISC-V
    riscv32    - 32-bit RISC-V
    riscv64    - 64-bit RISC-V

RISC-VのGCCツールチェーンをダウンロードしてPATHの通るところにおく必要がある。 ここではてきとうにgccというディレクトリを作ってそこに展開してる。 バージョンはちょっと古いものを使って試したが、もっと新しいやつもってきても大丈夫だと思われる。

$ mkdir -p gcc
$ curl -L https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-8.1.0-2018.12.0-x86_64-linux-ubuntu14.tar.gz | tar --strip-components=1 -C gcc -xz

シミュレータ環境としてこのへんを用意しておく。

やる

ソースコードはこんな感じだよ。

extern (C):

// version(CRuntime_Musl) とか定義してもいい。
int printf(scope const char* format, scope const ...);

int main()
{
    printf("Hello, World!\n");
    return 0;
}

コンパイルオプションはRISC-V 64向けだと以下のようにする。 -mattrの指定は整数乗除算(+m),アトミック命令(+a),16bit圧縮命令(+c)を指定。というかこれ以外だとgccのリンクの時にこけるんだよな。。新しいので試したほうがいいかもしれない。 あとLLVM 10だとCPUタイプをgeneric-rv64指定したときにrvcヒント命令(+rvc-hints)もついてくるけど、今回だと多分関係ない(はず)。

$ ldc2 --mtriple=riscv64-unknown-none-elf -mcpu=generic-rv64 \
  -mattr=+m,+a,+c -betterC -c hello.d
$ riscv64-unknown-elf-gcc -march=rv64imac -mabi=lp64 -o hello hello.o

シミュレータ上で動かす。

$ spike pk hello
bbl loader
Hello, World!
$

おまけ(読まなくてもいいやつ)

-mcpu=generic-rv64 指定ないとgenericというRISC-Vターゲットには存在しないCPUタイプ指定してくるのでこれが必須。upstreamのLDCではすでに修正されている。 あとLDCは--gcc指定でリンク処理するCコンパイラ指定できるんだけど、ArmとMIPS以外の環境だと勝手に-m32/-m64を付けてくるので上記のようにリンクは別コマンドでやる必要がある。 これもパッチはもうできててたぶんそのうち取り込まれる。

一応RISC-V 32版でのコンパイル方法ものせておくけど、spikeがバグってんのか--isa指定いろいろ試してもだめだった。(ここはほんとに読まなくていいとこ)

$ ldc2 --mtriple=riscv32-unknown-none-elf -mcpu=generic-rv32 \
  -mattr=+m,+a,+c -betterC -c hello.d
$ riscv64-unknown-elf-gcc -march=rv32imac -mabi=ilp32 -o hello hello.o

D言語もくもく会第17回

(追記: なんやかんやでWASI Tutorialまでできるようになった: https://github.com/kubo39/ldc-wasi-tutorial )

はじめて記事書くな、17です(威圧)。

これにだいたい参加しています。

雑にWASIをwasmtimeで動かせないかな、と試すやつをやりました。

まずWASI対応してるっぽいLDCのビルドからやる。skoppe氏がいろいろやっているはずなのでそこから持ってくる。

$ git submodule add -b wasm https://github.com/skoppe/ldc
$ cd ldc
$ git rev-parse HEAD
828926064c52eba905d9bbbf9d4d57f64a2cd267
$ git submodule init
$ git submodule update --recursive
$ cd ..
$ mkdir build-ldc && cd build-ldc
$ cmake -G Ninja ../ldc \
  -DCMAKE_BUILD_TYPE=Release \
  -DCMAKE_INSTALL_PREFIX=$PWD/../install-ldc
$ ninja -j$(nproc)

動かすにはCのランタイムとか必要なので、wasi-sdkをダウンロードする。ここではバージョン10をダウンロードしている。

$ wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-10/wasi-sdk-10.0-linux.tar.gz

WASIでコンパイル、やってみた!(プリチャン風)

import core.stdc.stdio;

extern(C) int main()
{
    printf("Hello, WASI!\n");
    return 0;
}

コンパイルオプションは勘ですがたぶんこんな感じになるはず。

$ ./build-ldc/bin/ldc2 -mtriple=wasm32-unknown-wasi -betterC --gcc=./wasi-sdk-10.0/bin/clang --linker=./wasi-sdk-10.0/bin/wasm-ld main.d
emit _start
/home/kubo39/dev/dlang/ldc-wasi-tutorial/ldc/runtime/druntime/src/core/internal/entrypoint.d(32): Error: only one `main` function allowed

はいエントリポイントまわりがいい感じになっていないぽいですね。

しゃあないのでパッチ書きます。

$ git diff
diff --git a/src/core/internal/entrypoint.d b/src/core/internal/entrypoint.d
index eb00b156..be3b204c 100644
--- a/src/core/internal/entrypoint.d
+++ b/src/core/internal/entrypoint.d
@@ -45,7 +45,8 @@ template _d_cmain()
                 return main(argc, argv);
             }
         }
-        version (WebAssembly)
+        version (WASI) {}
+        else version (WebAssembly)
           {
             pragma(msg, "emit _start");
             import ldc.attributes;

で、まあこのままだといろいろシンボル定義違うとか_startないよとかprintfないよとか怒られるのでコードも修正します。

import core.stdc.stdio;

extern(C):

void __prepare_for_exit() {}
void __wasi_proc_exit(int i) {}

pragma(mangle, "__original_main")
extern(C) int main()
{
    printf("Hello, WASI!\n");
    return 0;
}

動くとこまでいきました。やったぜ。

$ ./build-ldc/bin/ldc2 -mtriple=wasm32-unknown-wasi -betterC -L./wasi-sdk-10.0/share/wasi-sysroot/lib/wasm32-wasi/crt1.o -L./wasi-sdk-10.0/share/wasi-sysroot/lib/wasm32-wasi/libc.a --linker=./wasi-sdk-10.0/bin/wasm-ld main.d
$ wasmtime main.wasm
Hello, WASI!