kubo39's blog

ただの雑記です。

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

さらに続き。

もう少し調べてみるとリンカに渡す前のオブジェクトファイルはTypeセクションが意図したおりになっていた。

ldc2 -mtriple=wasm32-unknown-unknown \
    -mattr=+multivalue \
    -betterC \
    -fvisibility=hidden \
    --frame-pointer=none \
    -Oz \
    -c \
    multivalue.d

Typeセクションが壊れていないことが確認できる。

$ wasm-objdump -x multivalue.o

multivalue.o:   file format wasm 0x1

Section Details:

Type[1]:
 - type[0] () -> (i32, i32)
Import[2]:
 - memory[0] pages: initial=0 <- env.__linear_memory
 - table[0] type=funcref initial=0 <- env.__indirect_function_table
Function[1]:
 - func[0] sig=0 <return_two_i32>
Code[1]:
 - func[0] size=6 <return_two_i32>
Custom:
 - name: "linking"
  - symbol table [count=1]
   - 0: F <return_two_i32> func=0 binding=global vis=default
Custom:
 - name: "producers"
Custom:
 - name: "target_features"

じゃあこれがなぜ壊れるようになったのか。

リンカにverboseフラグを足してみよう。

$ ldc2 -mtriple=wasm32-unknown-unknown \
    -mattr=+multivalue \
    -betterC \
    -fvisibility=hidden \
    --frame-pointer=none \
    -Oz \
    -L=--no-entry \
    -L=--verbose \
    multivalue.d
lld: Loading: multivalue.o
lld: Processing: multivalue.o
lld: -- createOutputSegments
lld: -- createSyntheticSections
lld: -- populateProducers
lld: -- populateTargetFeatures
lld: -- calculateImports
lld: -- layoutMemory
lld: mem: global base = 1024
lld: mem: static data = 0
lld: mem: stack size  = 65536
lld: mem: stack base  = 1024
lld: mem: stack top   = 66560
lld: mem: heap base   = 66560
lld: mem: total pages = 2
lld: -- scanRelocations
lld: -- assignIndexes
lld: -- calculateInitFunctions
lld: -- calculateTypes
z3lld: -- calculateExports
lld: -- calculateCustomSections
lld: calculateCustomSections
lld: -- populateSymtab
lld: -- addSections
lld: addSection: TYPE
lld: addSection: FUNCTION
lld: addSection: TABLE
lld: addSection: MEMORY
lld: addSection: GLOBAL
lld: addSection: EXPORT
lld: addSection: CODE
lld: createCustomSections
lld: addSection: CUSTOM(name)
lld: addSection: CUSTOM(producers)
lld: addSection: CUSTOM(target_features)
lld: Defined Functions: 1
lld: Defined Globals  : 1
lld: Defined Events   : 0
lld: Function Imports : 0
lld: Global Imports   : 0
lld: Event Imports    : 0
lld: info for: multivalue.o
              Symbols : 1
     Function Imports : 0
       Global Imports : 0
        Event Imports : 0
lld: -- finalizeSections
lld: setOffset: TYPE: 8
lld: createHeader: TYPE body=5 total=7
lld: setOffset: FUNCTION: 15
lld: createHeader: FUNCTION body=2 total=4
lld: setOffset: TABLE: 19
lld: createHeader: TABLE body=5 total=7
lld: setOffset: MEMORY: 26
lld: createHeader: MEMORY body=3 total=5
lld: setOffset: GLOBAL: 31
lld: createHeader: GLOBAL body=8 total=10
lld: setOffset: EXPORT: 41
lld: createHeader: EXPORT body=27 total=29
lld: setOffset: CODE: 70
lld: createHeader: CODE body=8 total=10
lld: setOffset: CUSTOM(name): 80
lld: createHeader: CUSTOM(name) body=24 total=26
lld: setOffset: CUSTOM(producers): 106
lld: createHeader: CUSTOM(producers) body=54 total=56
lld: setOffset: CUSTOM(target_features): 162
lld: createHeader: CUSTOM(target_features) body=29 total=31
lld: -- openFile
lld: writing: multivalue.wasm
lld: -- writeSections
lld: writing CUSTOM(target_features)
lld: writing CUSTOM(producers)
lld: writing CUSTOM(name)
lld: writing CODE
lld:  size=10
lld:  headersize=2
lld:  codeheadersize=1
lld: writing EXPORT
lld: writing MEMORY
lld: writing GLOBAL
lld: writing FUNCTION
lld: writing TYPE
lld: writing TABLE

このあたりが怪しそう。

lld: -- finalizeSections
lld: setOffset: TYPE: 8
lld: createHeader: TYPE body=5 total=7
void Writer::finalizeSections() {
  for (OutputSection *s : outputSections) {
    s->setOffset(fileSize);
    s->finalizeContents();
    fileSize += s->getSize();  // fileSize += 7
  }
}

TypeSectionはSynctheticSectionを継承しているのでfinalizeContentsは以下のコードになる。

  void finalizeContents() override {
    writeBody();
    bodyOutputStream.flush();  // ここでbodyに書き込んでいる?
    createHeader(body.size()); // body.size()=5
  }

createHeaderのコードはまたOutputSectionに戻ってくる。 デバッグ出力の値を埋め込んでいるところをみると、引数のbodySizeがおかしいようだ。

void OutputSection::createHeader(size_t bodySize) {
  raw_string_ostream os(header);
  debugWrite(os.tell(), "section type [" + getSectionName() + "]");
  encodeULEB128(type, os);
  writeUleb128(os, bodySize, "section size");
  os.flush();
  log("createHeader: " + toString(*this) + " body=" + Twine(bodySize) +  // bodySize=5
      " total=" + Twine(getSize()));  // getSize()=7
}

さかのぼると、ここでbody sizeが5になっているのがおかしいようだ。

void TypeSection::writeBody() {
  writeUleb128(bodyOutputStream, types.size(), "type count");
  for (const WasmSignature *sig : types)
    writeSig(bodyOutputStream, *sig);  // ここ?
}

typesに入れてる箇所はここ。

uint32_t TypeSection::registerType(const WasmSignature &sig) {
  auto pair = typeIndices.insert(std::make_pair(sig, types.size()));
  if (pair.second) {
    LLVM_DEBUG(llvm::dbgs() << "type " << toString(sig) << "\n");
    types.push_back(&sig); // sigを入れてる。
  }
  return pair.first->second;
}

registerTypeはここで呼び出されている。

void Writer::calculateTypes() {
  // The output type section is the union of the following sets:
  // 1. Any signature used in the TYPE relocation
  // 2. The signatures of all imported functions
  // 3. The signatures of all defined functions
  // 4. The signatures of all imported events
  // 5. The signatures of all defined events

  for (ObjFile *file : symtab->objectFiles) {
    ArrayRef<WasmSignature> types = file->getWasmObj()->types();
    for (uint32_t i = 0; i < types.size(); i++)
      if (file->typeIsUsed[i])
        file->typeMap[i] = out.typeSec->registerType(types[i]);
  }

  for (const Symbol *sym : out.importSec->importedSymbols) {
    if (auto *f = dyn_cast<FunctionSymbol>(sym))
      out.typeSec->registerType(*f->signature);
    else if (auto *e = dyn_cast<EventSymbol>(sym))
      out.typeSec->registerType(*e->signature);
  }

  for (const InputFunction *f : out.functionSec->inputFunctions)
    out.typeSec->registerType(f->signature);

  for (const InputEvent *e : out.eventSec->inputEvents)
    out.typeSec->registerType(e->signature);
}

writeSigはここ。

void writeSig(raw_ostream &os, const WasmSignature &sig) {
  writeU8(os, WASM_TYPE_FUNC, "signature type");
  writeUleb128(os, sig.Params.size(), "param Count");
  for (ValType paramType : sig.Params) {
    writeValueType(os, paramType, "param type");
  }
  writeUleb128(os, sig.Returns.size(), "result Count"); // ここは2らしい
  for (ValType returnType : sig.Returns) {
    writeValueType(os, returnType, "result type");  // ここがおかしい?
  }
}

うーん、でも元のmultivalue.oをwasm-validateにかけると通るんだよな。わからん。