mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-26 15:56:07 +00:00

This change allows a WasmObjectFile to be created from a wasm file even if it uses typed funcrefs and GC types. It does not significantly change how lib/Object models its various internal types (e.g. WasmSignature, WasmElemSegment), so LLVM does not really "support" or understand such files, but it is sufficient to parse the type, global and element sections, discarding types that are not understood. This is useful for low-level binary tools such as nm and objcopy, which use only limited aspects of the binary (such as function definitions) or deal with sections as opaque blobs. This is done by allowing `WasmValType` to have a value of `OTHERREF` (representing any unmodeled reference type), and adding a field to `WasmSignature` indicating it's a placeholder for an unmodeled reference type (since there is a 1:1 correspondence between WasmSignature objects and types in the type section). Then the object file parsers for the type and element sections are expanded to parse encoded reference types and discard any unmodeled fields.
266 lines
8.0 KiB
C++
266 lines
8.0 KiB
C++
//===- WriterUtils.cpp ----------------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "WriterUtils.h"
|
|
#include "lld/Common/ErrorHandler.h"
|
|
#include "llvm/ADT/StringExtras.h"
|
|
#include "llvm/Support/Debug.h"
|
|
#include "llvm/Support/EndianStream.h"
|
|
#include "llvm/Support/LEB128.h"
|
|
|
|
#define DEBUG_TYPE "lld"
|
|
|
|
using namespace llvm;
|
|
using namespace llvm::wasm;
|
|
|
|
namespace lld {
|
|
std::string toString(ValType type) {
|
|
switch (type) {
|
|
case ValType::I32:
|
|
return "i32";
|
|
case ValType::I64:
|
|
return "i64";
|
|
case ValType::F32:
|
|
return "f32";
|
|
case ValType::F64:
|
|
return "f64";
|
|
case ValType::V128:
|
|
return "v128";
|
|
case ValType::FUNCREF:
|
|
return "funcref";
|
|
case ValType::EXTERNREF:
|
|
return "externref";
|
|
case ValType::OTHERREF:
|
|
return "otherref";
|
|
}
|
|
llvm_unreachable("Invalid wasm::ValType");
|
|
}
|
|
|
|
std::string toString(const WasmSignature &sig) {
|
|
SmallString<128> s("(");
|
|
for (ValType type : sig.Params) {
|
|
if (s.size() != 1)
|
|
s += ", ";
|
|
s += toString(type);
|
|
}
|
|
s += ") -> ";
|
|
if (sig.Returns.empty())
|
|
s += "void";
|
|
else
|
|
s += toString(sig.Returns[0]);
|
|
return std::string(s);
|
|
}
|
|
|
|
std::string toString(const WasmGlobalType &type) {
|
|
return (type.Mutable ? "var " : "const ") +
|
|
toString(static_cast<ValType>(type.Type));
|
|
}
|
|
|
|
static std::string toString(const llvm::wasm::WasmLimits &limits) {
|
|
std::string ret;
|
|
ret += "flags=0x" + std::to_string(limits.Flags);
|
|
ret += "; min=" + std::to_string(limits.Minimum);
|
|
if (limits.Flags & WASM_LIMITS_FLAG_HAS_MAX)
|
|
ret += "; max=" + std::to_string(limits.Maximum);
|
|
return ret;
|
|
}
|
|
|
|
std::string toString(const WasmTableType &type) {
|
|
SmallString<128> ret("");
|
|
return "type=" + toString(static_cast<ValType>(type.ElemType)) +
|
|
"; limits=[" + toString(type.Limits) + "]";
|
|
}
|
|
|
|
namespace wasm {
|
|
#ifdef LLVM_DEBUG
|
|
void debugWrite(uint64_t offset, const Twine &msg) {
|
|
LLVM_DEBUG(dbgs() << format(" | %08lld: ", offset) << msg << "\n");
|
|
}
|
|
#endif
|
|
|
|
void writeUleb128(raw_ostream &os, uint64_t number, const Twine &msg) {
|
|
debugWrite(os.tell(), msg + "[" + utohexstr(number) + "]");
|
|
encodeULEB128(number, os);
|
|
}
|
|
|
|
void writeSleb128(raw_ostream &os, int64_t number, const Twine &msg) {
|
|
debugWrite(os.tell(), msg + "[" + utohexstr(number) + "]");
|
|
encodeSLEB128(number, os);
|
|
}
|
|
|
|
void writeBytes(raw_ostream &os, const char *bytes, size_t count,
|
|
const Twine &msg) {
|
|
debugWrite(os.tell(), msg + " [data[" + Twine(count) + "]]");
|
|
os.write(bytes, count);
|
|
}
|
|
|
|
void writeStr(raw_ostream &os, StringRef string, const Twine &msg) {
|
|
debugWrite(os.tell(),
|
|
msg + " [str[" + Twine(string.size()) + "]: " + string + "]");
|
|
encodeULEB128(string.size(), os);
|
|
os.write(string.data(), string.size());
|
|
}
|
|
|
|
void writeU8(raw_ostream &os, uint8_t byte, const Twine &msg) {
|
|
debugWrite(os.tell(), msg + " [0x" + utohexstr(byte) + "]");
|
|
os << byte;
|
|
}
|
|
|
|
void writeU32(raw_ostream &os, uint32_t number, const Twine &msg) {
|
|
debugWrite(os.tell(), msg + "[0x" + utohexstr(number) + "]");
|
|
support::endian::write(os, number, llvm::endianness::little);
|
|
}
|
|
|
|
void writeU64(raw_ostream &os, uint64_t number, const Twine &msg) {
|
|
debugWrite(os.tell(), msg + "[0x" + utohexstr(number) + "]");
|
|
support::endian::write(os, number, llvm::endianness::little);
|
|
}
|
|
|
|
void writeValueType(raw_ostream &os, ValType type, const Twine &msg) {
|
|
writeU8(os, static_cast<uint8_t>(type),
|
|
msg + "[type: " + toString(type) + "]");
|
|
}
|
|
|
|
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");
|
|
for (ValType returnType : sig.Returns) {
|
|
writeValueType(os, returnType, "result type");
|
|
}
|
|
}
|
|
|
|
void writeI32Const(raw_ostream &os, int32_t number, const Twine &msg) {
|
|
writeU8(os, WASM_OPCODE_I32_CONST, "i32.const");
|
|
writeSleb128(os, number, msg);
|
|
}
|
|
|
|
void writeI64Const(raw_ostream &os, int64_t number, const Twine &msg) {
|
|
writeU8(os, WASM_OPCODE_I64_CONST, "i64.const");
|
|
writeSleb128(os, number, msg);
|
|
}
|
|
|
|
void writePtrConst(raw_ostream &os, int64_t number, bool is64,
|
|
const Twine &msg) {
|
|
if (is64)
|
|
writeI64Const(os, number, msg);
|
|
else
|
|
writeI32Const(os, static_cast<int32_t>(number), msg);
|
|
}
|
|
|
|
void writeMemArg(raw_ostream &os, uint32_t alignment, uint64_t offset) {
|
|
writeUleb128(os, alignment, "alignment");
|
|
writeUleb128(os, offset, "offset");
|
|
}
|
|
|
|
void writeInitExpr(raw_ostream &os, const WasmInitExpr &initExpr) {
|
|
assert(!initExpr.Extended);
|
|
writeInitExprMVP(os, initExpr.Inst);
|
|
}
|
|
|
|
void writeInitExprMVP(raw_ostream &os, const WasmInitExprMVP &initExpr) {
|
|
writeU8(os, initExpr.Opcode, "opcode");
|
|
switch (initExpr.Opcode) {
|
|
case WASM_OPCODE_I32_CONST:
|
|
writeSleb128(os, initExpr.Value.Int32, "literal (i32)");
|
|
break;
|
|
case WASM_OPCODE_I64_CONST:
|
|
writeSleb128(os, initExpr.Value.Int64, "literal (i64)");
|
|
break;
|
|
case WASM_OPCODE_F32_CONST:
|
|
writeU32(os, initExpr.Value.Float32, "literal (f32)");
|
|
break;
|
|
case WASM_OPCODE_F64_CONST:
|
|
writeU64(os, initExpr.Value.Float64, "literal (f64)");
|
|
break;
|
|
case WASM_OPCODE_GLOBAL_GET:
|
|
writeUleb128(os, initExpr.Value.Global, "literal (global index)");
|
|
break;
|
|
case WASM_OPCODE_REF_NULL:
|
|
writeValueType(os, ValType::EXTERNREF, "literal (externref type)");
|
|
break;
|
|
default:
|
|
fatal("unknown opcode in init expr: " + Twine(initExpr.Opcode));
|
|
}
|
|
writeU8(os, WASM_OPCODE_END, "opcode:end");
|
|
}
|
|
|
|
void writeLimits(raw_ostream &os, const WasmLimits &limits) {
|
|
writeU8(os, limits.Flags, "limits flags");
|
|
writeUleb128(os, limits.Minimum, "limits min");
|
|
if (limits.Flags & WASM_LIMITS_FLAG_HAS_MAX)
|
|
writeUleb128(os, limits.Maximum, "limits max");
|
|
}
|
|
|
|
void writeGlobalType(raw_ostream &os, const WasmGlobalType &type) {
|
|
// TODO: Update WasmGlobalType to use ValType and remove this cast.
|
|
writeValueType(os, ValType(type.Type), "global type");
|
|
writeU8(os, type.Mutable, "global mutable");
|
|
}
|
|
|
|
void writeTableType(raw_ostream &os, const WasmTableType &type) {
|
|
writeValueType(os, ValType(type.ElemType), "table type");
|
|
writeLimits(os, type.Limits);
|
|
}
|
|
|
|
void writeImport(raw_ostream &os, const WasmImport &import) {
|
|
writeStr(os, import.Module, "import module name");
|
|
writeStr(os, import.Field, "import field name");
|
|
writeU8(os, import.Kind, "import kind");
|
|
switch (import.Kind) {
|
|
case WASM_EXTERNAL_FUNCTION:
|
|
writeUleb128(os, import.SigIndex, "import sig index");
|
|
break;
|
|
case WASM_EXTERNAL_GLOBAL:
|
|
writeGlobalType(os, import.Global);
|
|
break;
|
|
case WASM_EXTERNAL_TAG:
|
|
writeUleb128(os, 0, "tag attribute"); // Reserved "attribute" field
|
|
writeUleb128(os, import.SigIndex, "import sig index");
|
|
break;
|
|
case WASM_EXTERNAL_MEMORY:
|
|
writeLimits(os, import.Memory);
|
|
break;
|
|
case WASM_EXTERNAL_TABLE:
|
|
writeTableType(os, import.Table);
|
|
break;
|
|
default:
|
|
fatal("unsupported import type: " + Twine(import.Kind));
|
|
}
|
|
}
|
|
|
|
void writeExport(raw_ostream &os, const WasmExport &export_) {
|
|
writeStr(os, export_.Name, "export name");
|
|
writeU8(os, export_.Kind, "export kind");
|
|
switch (export_.Kind) {
|
|
case WASM_EXTERNAL_FUNCTION:
|
|
writeUleb128(os, export_.Index, "function index");
|
|
break;
|
|
case WASM_EXTERNAL_GLOBAL:
|
|
writeUleb128(os, export_.Index, "global index");
|
|
break;
|
|
case WASM_EXTERNAL_TAG:
|
|
writeUleb128(os, export_.Index, "tag index");
|
|
break;
|
|
case WASM_EXTERNAL_MEMORY:
|
|
writeUleb128(os, export_.Index, "memory index");
|
|
break;
|
|
case WASM_EXTERNAL_TABLE:
|
|
writeUleb128(os, export_.Index, "table index");
|
|
break;
|
|
default:
|
|
fatal("unsupported export type: " + Twine(export_.Kind));
|
|
}
|
|
}
|
|
|
|
} // namespace wasm
|
|
} // namespace lld
|