
This commit adds support for WebAssembly's custom-page-sizes proposal to `wasm-ld`. An overview of the proposal can be found [here](https://github.com/WebAssembly/custom-page-sizes/blob/main/proposals/custom-page-sizes/Overview.md). In a sentence, it allows customizing a Wasm memory's page size, enabling Wasm to target environments with less than 64KiB of memory (the default Wasm page size) available for Wasm memories. This commit contains the following: * Adds a `--page-size=N` CLI flag to `wasm-ld` for configuring the linked Wasm binary's linear memory's page size. * When the page size is configured to a non-default value, then the final Wasm binary will use the encodings defined in the custom-page-sizes proposal to declare the linear memory's page size. * Defines a `__wasm_first_page_end` symbol, whose address points to the first page in the Wasm linear memory, a.k.a. is the Wasm memory's page size. This allows writing code that is compatible with any page size, and doesn't require re-compiling its object code. At the same time, because it just lowers to a constant rather than a memory access or something, it enables link-time optimization. * Adds tests for these new features. r? @sbc100 cc @sunfishcode
272 lines
8.2 KiB
C++
272 lines
8.2 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::EXNREF:
|
|
return "exnref";
|
|
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);
|
|
if (limits.Flags & WASM_LIMITS_FLAG_HAS_PAGE_SIZE)
|
|
ret += "; pagesize=" + std::to_string(limits.PageSize);
|
|
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");
|
|
if (limits.Flags & WASM_LIMITS_FLAG_HAS_PAGE_SIZE)
|
|
writeUleb128(os, llvm::Log2_64(limits.PageSize), "page size");
|
|
}
|
|
|
|
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
|