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のコード(2021/04/17最終更新 コミットハッシュ:e6deb6c61f087553b58028ccab41c20c729a6b90)をみると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
ひとまずここまで。続くかもしれないし続かないかもしれない。