blob: ab93eb374267f867268c00b86c95a0e3ef1ae00e [file] [log] [blame]
//===- subzero/src/WasmTranslator.cpp - WASM to Subzero Translation -------===//
//
// The Subzero Code Generator
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \brief Defines a driver for translating Wasm bitcode into PNaCl bitcode.
///
/// The translator uses V8's WebAssembly decoder to handle the binary Wasm
/// format but replaces the usual TurboFan builder with a new PNaCl builder.
///
//===----------------------------------------------------------------------===//
#if ALLOW_WASM
#include "WasmTranslator.h"
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter"
#pragma clang diagnostic ignored "-Wcovered-switch-default"
#endif // __clang__
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#endif // defined(__GNUC__) && !defined(__clang__)
#include "src/wasm/module-decoder.h"
#include "src/wasm/wasm-opcodes.h"
#include "src/zone.h"
#include "src/bit-vector.h"
#include "src/wasm/ast-decoder-impl.h"
#ifdef __clang__
#pragma clang diagnostic pop
#endif // __clang__
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif // defined(__GNUC__) && !defined(__clang__)
#include "IceCfgNode.h"
#include "IceGlobalInits.h"
using namespace std;
using namespace Ice;
using namespace v8::internal;
using namespace v8::internal::wasm;
using v8::internal::wasm::DecodeWasmModule;
#undef LOG
#define LOG(Expr) log([&](Ostream &out) { Expr; })
namespace {
// 64KB
const uint32_t WASM_PAGE_SIZE = 64 << 10;
std::string toStdString(WasmName Name) {
return std::string(Name.name, Name.length);
}
Ice::Type toIceType(wasm::LocalType Type) {
switch (Type) {
case MachineRepresentation::kNone:
llvm::report_fatal_error("kNone type not supported");
case MachineRepresentation::kBit:
return IceType_i1;
case MachineRepresentation::kWord8:
return IceType_i8;
case MachineRepresentation::kWord16:
return IceType_i16;
case MachineRepresentation::kWord32:
return IceType_i32;
case MachineRepresentation::kWord64:
return IceType_i64;
case MachineRepresentation::kFloat32:
return IceType_f32;
case MachineRepresentation::kFloat64:
return IceType_f64;
case MachineRepresentation::kSimd128:
llvm::report_fatal_error("ambiguous SIMD type");
case MachineRepresentation::kTagged:
llvm::report_fatal_error("kTagged type not supported");
}
llvm::report_fatal_error("unexpected type");
}
Ice::Type toIceType(v8::internal::MachineType Type) {
// TODO (eholk): reorder these based on expected call frequency.
if (Type == MachineType::Int32()) {
return IceType_i32;
}
if (Type == MachineType::Uint32()) {
return IceType_i32;
}
if (Type == MachineType::Int8()) {
return IceType_i8;
}
if (Type == MachineType::Uint8()) {
return IceType_i8;
}
if (Type == MachineType::Int16()) {
return IceType_i16;
}
if (Type == MachineType::Uint16()) {
return IceType_i16;
}
if (Type == MachineType::Int64()) {
return IceType_i64;
}
if (Type == MachineType::Uint64()) {
return IceType_i64;
}
if (Type == MachineType::Float32()) {
return IceType_f32;
}
if (Type == MachineType::Float64()) {
return IceType_f64;
}
llvm::report_fatal_error("Unsupported MachineType");
}
std::string fnNameFromId(uint32_t Id) {
return std::string("fn") + to_string(Id);
}
std::string getFunctionName(const WasmModule *Module, uint32_t func_index) {
// Try to find the function name in the export table
for (const auto Export : Module->export_table) {
if (Export.func_index == func_index) {
return "__szwasm_" + toStdString(Module->GetName(Export.name_offset,
Export.name_length));
}
}
return fnNameFromId(func_index);
}
} // end of anonymous namespace
/// This class wraps either an Operand or a CfgNode.
///
/// Turbofan's sea of nodes representation only has nodes for values, control
/// flow, etc. In Subzero these concepts are all separate. This class lets V8's
/// Wasm decoder treat Subzero objects as though they are all the same.
class OperandNode {
static constexpr uintptr_t NODE_FLAG = 1;
static constexpr uintptr_t UNDEF_PTR = (uintptr_t)-1;
uintptr_t Data = UNDEF_PTR;
public:
OperandNode() = default;
explicit OperandNode(Operand *Operand)
: Data(reinterpret_cast<uintptr_t>(Operand)) {}
explicit OperandNode(CfgNode *Node)
: Data(reinterpret_cast<uintptr_t>(Node) | NODE_FLAG) {}
explicit OperandNode(nullptr_t) : Data(UNDEF_PTR) {}
operator Operand *() const {
if (UNDEF_PTR == Data) {
return nullptr;
}
if (!isOperand()) {
llvm::report_fatal_error("This OperandNode is not an Operand");
}
return reinterpret_cast<Operand *>(Data);
}
operator CfgNode *() const {
if (UNDEF_PTR == Data) {
return nullptr;
}
if (!isCfgNode()) {
llvm::report_fatal_error("This OperandNode is not a CfgNode");
}
return reinterpret_cast<CfgNode *>(Data & ~NODE_FLAG);
}
explicit operator bool() const { return (Data != UNDEF_PTR) && Data; }
bool operator==(const OperandNode &Rhs) const {
return (Data == Rhs.Data) ||
(UNDEF_PTR == Data && (Rhs.Data == 0 || Rhs.Data == NODE_FLAG)) ||
(UNDEF_PTR == Rhs.Data && (Data == 0 || Data == NODE_FLAG));
}
bool operator!=(const OperandNode &Rhs) const { return !(*this == Rhs); }
bool isOperand() const { return (Data != UNDEF_PTR) && !(Data & NODE_FLAG); }
bool isCfgNode() const { return (Data != UNDEF_PTR) && (Data & NODE_FLAG); }
Operand *toOperand() const { return static_cast<Operand *>(*this); }
CfgNode *toCfgNode() const { return static_cast<CfgNode *>(*this); }
};
Ostream &operator<<(Ostream &Out, const OperandNode &Op) {
if (Op.isOperand()) {
const auto *Oper = Op.toOperand();
Out << "(Operand*)" << Oper;
if (Oper) {
Out << "::" << Oper->getType();
}
} else if (Op.isCfgNode()) {
Out << "(CfgNode*)" << Op.toCfgNode();
} else {
Out << "nullptr";
}
return Out;
}
bool isComparison(wasm::WasmOpcode Opcode) {
switch (Opcode) {
case kExprI32Ne:
case kExprI64Ne:
case kExprI32Eq:
case kExprI64Eq:
case kExprI32LtS:
case kExprI64LtS:
case kExprI32LtU:
case kExprI64LtU:
case kExprI32GeS:
case kExprI64GeS:
case kExprI32GtS:
case kExprI64GtS:
case kExprI32GtU:
case kExprI64GtU:
case kExprF32Eq:
case kExprF64Eq:
case kExprF32Ne:
case kExprF64Ne:
case kExprF32Le:
case kExprF64Le:
case kExprF32Lt:
case kExprF64Lt:
case kExprF32Ge:
case kExprF64Ge:
case kExprF32Gt:
case kExprF64Gt:
case kExprI32LeS:
case kExprI64LeS:
case kExprI32GeU:
case kExprI64GeU:
case kExprI32LeU:
case kExprI64LeU:
return true;
default:
return false;
}
}
class IceBuilder {
using Node = OperandNode;
using Variable = Ice::Variable;
IceBuilder() = delete;
IceBuilder(const IceBuilder &) = delete;
IceBuilder &operator=(const IceBuilder &) = delete;
public:
explicit IceBuilder(class Cfg *Func)
: ControlPtr(nullptr), Func(Func), Ctx(Func->getContext()) {}
/// Allocates a buffer of Nodes for use by V8.
Node *Buffer(size_t Count) {
LOG(out << "Buffer(" << Count << ")\n");
return Func->allocateArrayOf<Node>(Count);
}
Node Error() { llvm::report_fatal_error("Error"); }
Node Start(uint32_t Params) {
LOG(out << "Start(" << Params << ") = ");
auto *Entry = Func->getEntryNode();
assert(Entry);
LOG(out << Node(Entry) << "\n");
// Load the WasmMemory address to make it available everywhere else in the
// function.
auto *WasmMemoryPtr =
Ctx->getConstantExternSym(Ctx->getGlobalString("WASM_MEMORY"));
assert(WasmMemory == nullptr);
auto *WasmMemoryV = makeVariable(getPointerType());
Entry->appendInst(InstLoad::create(Func, WasmMemoryV, WasmMemoryPtr));
WasmMemory = WasmMemoryV;
return OperandNode(Entry);
}
Node Param(uint32_t Index, wasm::LocalType Type) {
LOG(out << "Param(" << Index << ") = ");
auto *Arg = makeVariable(toIceType(Type));
assert(Index == NextArg);
Func->addArg(Arg);
++NextArg;
LOG(out << Node(Arg) << "\n");
return OperandNode(Arg);
}
Node Loop(CfgNode *Entry) {
auto *Loop = Func->makeNode();
LOG(out << "Loop(" << Entry << ") = " << Loop << "\n");
Entry->appendInst(InstBr::create(Func, Loop));
return OperandNode(Loop);
}
void Terminate(Node Effect, Node Control) {
// TODO(eholk): this is almost certainly wrong
LOG(out << "Terminate(" << Effect << ", " << Control << ")"
<< "\n");
}
Node Merge(uint32_t Count, Node *Controls) {
LOG(out << "Merge(" << Count);
for (uint32_t i = 0; i < Count; ++i) {
LOG(out << ", " << Controls[i]);
}
LOG(out << ") = ");
auto *MergedNode = Func->makeNode();
for (uint32_t i = 0; i < Count; ++i) {
CfgNode *Control = Controls[i];
Control->appendInst(InstBr::create(Func, MergedNode));
}
LOG(out << (OperandNode)MergedNode << "\n");
return OperandNode(MergedNode);
}
Node Phi(wasm::LocalType, uint32_t Count, Node *Vals, Node Control) {
LOG(out << "Phi(" << Count << ", " << Control);
for (uint32_t i = 0; i < Count; ++i) {
LOG(out << ", " << Vals[i]);
}
LOG(out << ") = ");
const auto &InEdges = Control.toCfgNode()->getInEdges();
assert(Count == InEdges.size());
assert(Count > 0);
auto *Dest = makeVariable(Vals[0].toOperand()->getType(), Control);
// Multiply by 200 in case more things get added later.
// TODO(eholk): find a better way besides multiplying by some arbitrary
// constant.
auto *Phi = InstPhi::create(Func, Count * 200, Dest);
for (uint32_t i = 0; i < Count; ++i) {
auto *Op = Vals[i].toOperand();
assert(Op);
Phi->addArgument(Op, InEdges[i]);
}
setDefiningInst(Dest, Phi);
Control.toCfgNode()->appendInst(Phi);
LOG(out << Node(Dest) << "\n");
return OperandNode(Dest);
}
Node EffectPhi(uint32_t Count, Node *Effects, Node Control) {
// TODO(eholk): this function is almost certainly wrong.
LOG(out << "EffectPhi(" << Count << ", " << Control << "):\n");
for (uint32_t i = 0; i < Count; ++i) {
LOG(out << " " << Effects[i] << "\n");
}
return OperandNode(nullptr);
}
Node Int32Constant(int32_t Value) {
LOG(out << "Int32Constant(" << Value << ") = ");
auto *Const = Ctx->getConstantInt32(Value);
assert(Const);
assert(Control());
LOG(out << Node(Const) << "\n");
return OperandNode(Const);
}
Node Int64Constant(int64_t Value) {
LOG(out << "Int64Constant(" << Value << ") = ");
auto *Const = Ctx->getConstantInt64(Value);
assert(Const);
LOG(out << Node(Const) << "\n");
return OperandNode(Const);
}
Node Float32Constant(float Value) {
LOG(out << "Float32Constant(" << Value << ") = ");
auto *Const = Ctx->getConstantFloat(Value);
assert(Const);
LOG(out << Node(Const) << "\n");
return OperandNode(Const);
}
Node Float64Constant(double Value) {
LOG(out << "Float64Constant(" << Value << ") = ");
auto *Const = Ctx->getConstantDouble(Value);
assert(Const);
LOG(out << Node(Const) << "\n");
return OperandNode(Const);
}
Node Binop(wasm::WasmOpcode Opcode, Node Left, Node Right) {
LOG(out << "Binop(" << WasmOpcodes::OpcodeName(Opcode) << ", " << Left
<< ", " << Right << ") = ");
BooleanVariable *BoolDest = nullptr;
Variable *Dest = nullptr;
if (isComparison(Opcode)) {
BoolDest = makeVariable<BooleanVariable>(IceType_i32);
Dest = BoolDest;
} else {
Dest = makeVariable(Left.toOperand()->getType());
}
switch (Opcode) {
case kExprI32Add:
case kExprI64Add:
Control()->appendInst(
InstArithmetic::create(Func, InstArithmetic::Add, Dest, Left, Right));
break;
case kExprF32Add:
case kExprF64Add:
Control()->appendInst(InstArithmetic::create(Func, InstArithmetic::Fadd,
Dest, Left, Right));
break;
case kExprI32Sub:
case kExprI64Sub:
Control()->appendInst(
InstArithmetic::create(Func, InstArithmetic::Sub, Dest, Left, Right));
break;
case kExprF32Sub:
case kExprF64Sub:
Control()->appendInst(InstArithmetic::create(Func, InstArithmetic::Fsub,
Dest, Left, Right));
break;
case kExprI32Mul:
case kExprI64Mul:
Control()->appendInst(
InstArithmetic::create(Func, InstArithmetic::Mul, Dest, Left, Right));
break;
case kExprF32Mul:
case kExprF64Mul:
Control()->appendInst(InstArithmetic::create(Func, InstArithmetic::Fmul,
Dest, Left, Right));
break;
case kExprI32DivS:
case kExprI64DivS:
Control()->appendInst(InstArithmetic::create(Func, InstArithmetic::Sdiv,
Dest, Left, Right));
break;
case kExprI32DivU:
case kExprI64DivU:
Control()->appendInst(InstArithmetic::create(Func, InstArithmetic::Udiv,
Dest, Left, Right));
break;
case kExprF32Div:
case kExprF64Div:
Control()->appendInst(InstArithmetic::create(Func, InstArithmetic::Fdiv,
Dest, Left, Right));
break;
case kExprI32RemU:
case kExprI64RemU:
Control()->appendInst(InstArithmetic::create(Func, InstArithmetic::Urem,
Dest, Left, Right));
break;
case kExprI32RemS:
case kExprI64RemS:
Control()->appendInst(InstArithmetic::create(Func, InstArithmetic::Srem,
Dest, Left, Right));
break;
case kExprI32Ior:
case kExprI64Ior:
Control()->appendInst(
InstArithmetic::create(Func, InstArithmetic::Or, Dest, Left, Right));
break;
case kExprI32Xor:
case kExprI64Xor:
Control()->appendInst(
InstArithmetic::create(Func, InstArithmetic::Xor, Dest, Left, Right));
break;
case kExprI32Shl:
case kExprI64Shl:
Control()->appendInst(
InstArithmetic::create(Func, InstArithmetic::Shl, Dest, Left, Right));
break;
case kExprI32Rol:
case kExprI64Rol: {
// TODO(eholk): add rotate as an ICE instruction to make it easier to take
// advantage of hardware support.
const auto DestTy = Left.toOperand()->getType();
const SizeT BitCount = typeWidthInBytes(DestTy) * CHAR_BIT;
auto *Masked = makeVariable(DestTy);
auto *Bottom = makeVariable(DestTy);
auto *Top = makeVariable(DestTy);
Control()->appendInst(
InstArithmetic::create(Func, InstArithmetic::And, Masked, Right,
Ctx->getConstantInt(DestTy, BitCount - 1)));
Control()->appendInst(
InstArithmetic::create(Func, InstArithmetic::Shl, Top, Left, Masked));
auto *RotShift = makeVariable(DestTy);
Control()->appendInst(InstArithmetic::create(
Func, InstArithmetic::Sub, RotShift,
Ctx->getConstantInt(DestTy, BitCount), Masked));
Control()->appendInst(InstArithmetic::create(Func, InstArithmetic::Lshr,
Bottom, Left, RotShift));
Control()->appendInst(
InstArithmetic::create(Func, InstArithmetic::Or, Dest, Top, Bottom));
break;
}
case kExprI32Ror:
case kExprI64Ror: {
// TODO(eholk): add rotate as an ICE instruction to make it easier to take
// advantage of hardware support.
const auto DestTy = Left.toOperand()->getType();
const SizeT BitCount = typeWidthInBytes(DestTy) * CHAR_BIT;
auto *Masked = makeVariable(DestTy);
auto *Bottom = makeVariable(DestTy);
auto *Top = makeVariable(DestTy);
Control()->appendInst(
InstArithmetic::create(Func, InstArithmetic::And, Masked, Right,
Ctx->getConstantInt(DestTy, BitCount - 1)));
Control()->appendInst(InstArithmetic::create(Func, InstArithmetic::Lshr,
Top, Left, Masked));
auto *RotShift = makeVariable(DestTy);
Control()->appendInst(InstArithmetic::create(
Func, InstArithmetic::Sub, RotShift,
Ctx->getConstantInt(DestTy, BitCount), Masked));
Control()->appendInst(InstArithmetic::create(Func, InstArithmetic::Shl,
Bottom, Left, RotShift));
Control()->appendInst(
InstArithmetic::create(Func, InstArithmetic::Or, Dest, Top, Bottom));
break;
}
case kExprI32ShrU:
case kExprI64ShrU:
Control()->appendInst(InstArithmetic::create(Func, InstArithmetic::Lshr,
Dest, Left, Right));
break;
case kExprI32ShrS:
case kExprI64ShrS:
Control()->appendInst(InstArithmetic::create(Func, InstArithmetic::Ashr,
Dest, Left, Right));
break;
case kExprI32And:
case kExprI64And:
Control()->appendInst(
InstArithmetic::create(Func, InstArithmetic::And, Dest, Left, Right));
break;
case kExprI32Ne:
case kExprI64Ne: {
auto *TmpDest = makeVariable(IceType_i1);
Control()->appendInst(
InstIcmp::create(Func, InstIcmp::Ne, TmpDest, Left, Right));
BoolDest->setBoolSource(TmpDest);
Control()->appendInst(
InstCast::create(Func, InstCast::Zext, Dest, TmpDest));
break;
}
case kExprI32Eq:
case kExprI64Eq: {
auto *TmpDest = makeVariable(IceType_i1);
Control()->appendInst(
InstIcmp::create(Func, InstIcmp::Eq, TmpDest, Left, Right));
BoolDest->setBoolSource(TmpDest);
Control()->appendInst(
InstCast::create(Func, InstCast::Zext, Dest, TmpDest));
break;
}
case kExprI32LtS:
case kExprI64LtS: {
auto *TmpDest = makeVariable(IceType_i1);
Control()->appendInst(
InstIcmp::create(Func, InstIcmp::Slt, TmpDest, Left, Right));
BoolDest->setBoolSource(TmpDest);
Control()->appendInst(
InstCast::create(Func, InstCast::Zext, Dest, TmpDest));
break;
}
case kExprI32LeS:
case kExprI64LeS: {
auto *TmpDest = makeVariable(IceType_i1);
Control()->appendInst(
InstIcmp::create(Func, InstIcmp::Sle, TmpDest, Left, Right));
BoolDest->setBoolSource(TmpDest);
Control()->appendInst(
InstCast::create(Func, InstCast::Zext, Dest, TmpDest));
break;
}
case kExprI32GeU:
case kExprI64GeU: {
auto *TmpDest = makeVariable(IceType_i1);
Control()->appendInst(
InstIcmp::create(Func, InstIcmp::Uge, TmpDest, Left, Right));
BoolDest->setBoolSource(TmpDest);
Control()->appendInst(
InstCast::create(Func, InstCast::Zext, Dest, TmpDest));
break;
}
case kExprI32LeU:
case kExprI64LeU: {
auto *TmpDest = makeVariable(IceType_i1);
Control()->appendInst(
InstIcmp::create(Func, InstIcmp::Ule, TmpDest, Left, Right));
BoolDest->setBoolSource(TmpDest);
Control()->appendInst(
InstCast::create(Func, InstCast::Zext, Dest, TmpDest));
break;
}
case kExprI32LtU:
case kExprI64LtU: {
auto *TmpDest = makeVariable(IceType_i1);
Control()->appendInst(
InstIcmp::create(Func, InstIcmp::Ult, TmpDest, Left, Right));
BoolDest->setBoolSource(TmpDest);
Control()->appendInst(
InstCast::create(Func, InstCast::Zext, Dest, TmpDest));
break;
}
case kExprI32GeS:
case kExprI64GeS: {
auto *TmpDest = makeVariable(IceType_i1);
Control()->appendInst(
InstIcmp::create(Func, InstIcmp::Sge, TmpDest, Left, Right));
BoolDest->setBoolSource(TmpDest);
Control()->appendInst(
InstCast::create(Func, InstCast::Zext, Dest, TmpDest));
break;
}
case kExprI32GtS:
case kExprI64GtS: {
auto *TmpDest = makeVariable(IceType_i1);
Control()->appendInst(
InstIcmp::create(Func, InstIcmp::Sgt, TmpDest, Left, Right));
BoolDest->setBoolSource(TmpDest);
Control()->appendInst(
InstCast::create(Func, InstCast::Zext, Dest, TmpDest));
break;
}
case kExprI32GtU:
case kExprI64GtU: {
auto *TmpDest = makeVariable(IceType_i1);
Control()->appendInst(
InstIcmp::create(Func, InstIcmp::Ugt, TmpDest, Left, Right));
BoolDest->setBoolSource(TmpDest);
Control()->appendInst(
InstCast::create(Func, InstCast::Zext, Dest, TmpDest));
break;
}
case kExprF32Eq:
case kExprF64Eq: {
auto *TmpDest = makeVariable(IceType_i1);
Control()->appendInst(
InstFcmp::create(Func, InstFcmp::Ueq, TmpDest, Left, Right));
BoolDest->setBoolSource(TmpDest);
Control()->appendInst(
InstCast::create(Func, InstCast::Zext, Dest, TmpDest));
break;
}
case kExprF32Ne:
case kExprF64Ne: {
auto *TmpDest = makeVariable(IceType_i1);
Control()->appendInst(
InstFcmp::create(Func, InstFcmp::Une, TmpDest, Left, Right));
BoolDest->setBoolSource(TmpDest);
Control()->appendInst(
InstCast::create(Func, InstCast::Zext, Dest, TmpDest));
break;
}
case kExprF32Le:
case kExprF64Le: {
auto *TmpDest = makeVariable(IceType_i1);
Control()->appendInst(
InstFcmp::create(Func, InstFcmp::Ule, TmpDest, Left, Right));
BoolDest->setBoolSource(TmpDest);
Control()->appendInst(
InstCast::create(Func, InstCast::Zext, Dest, TmpDest));
break;
}
case kExprF32Lt:
case kExprF64Lt: {
auto *TmpDest = makeVariable(IceType_i1);
Control()->appendInst(
InstFcmp::create(Func, InstFcmp::Ult, TmpDest, Left, Right));
BoolDest->setBoolSource(TmpDest);
Control()->appendInst(
InstCast::create(Func, InstCast::Zext, Dest, TmpDest));
break;
}
case kExprF32Ge:
case kExprF64Ge: {
auto *TmpDest = makeVariable(IceType_i1);
Control()->appendInst(
InstFcmp::create(Func, InstFcmp::Uge, TmpDest, Left, Right));
BoolDest->setBoolSource(TmpDest);
Control()->appendInst(
InstCast::create(Func, InstCast::Zext, Dest, TmpDest));
break;
}
case kExprF32Gt:
case kExprF64Gt: {
auto *TmpDest = makeVariable(IceType_i1);
Control()->appendInst(
InstFcmp::create(Func, InstFcmp::Ugt, TmpDest, Left, Right));
BoolDest->setBoolSource(TmpDest);
Control()->appendInst(
InstCast::create(Func, InstCast::Zext, Dest, TmpDest));
break;
}
default:
LOG(out << "Unknown binop: " << WasmOpcodes::OpcodeName(Opcode) << "\n");
llvm::report_fatal_error("Uncovered or invalid binop.");
return OperandNode(nullptr);
}
LOG(out << Dest << "\n");
return OperandNode(Dest);
}
Node Unop(wasm::WasmOpcode Opcode, Node Input) {
LOG(out << "Unop(" << WasmOpcodes::OpcodeName(Opcode) << ", " << Input
<< ") = ");
Variable *Dest = nullptr;
switch (Opcode) {
// TODO (eholk): merge these next two cases using getConstantInteger
case kExprI32Eqz: {
auto *BoolDest = makeVariable<BooleanVariable>(IceType_i32);
Dest = BoolDest;
auto *Tmp = makeVariable(IceType_i1);
Control()->appendInst(InstIcmp::create(Func, InstIcmp::Eq, Tmp, Input,
Ctx->getConstantInt32(0)));
BoolDest->setBoolSource(Tmp);
Control()->appendInst(InstCast::create(Func, InstCast::Zext, Dest, Tmp));
break;
}
case kExprI64Eqz: {
auto *BoolDest = makeVariable<BooleanVariable>(IceType_i32);
Dest = BoolDest;
auto *Tmp = makeVariable(IceType_i1);
Control()->appendInst(InstIcmp::create(Func, InstIcmp::Eq, Tmp, Input,
Ctx->getConstantInt64(0)));
BoolDest->setBoolSource(Tmp);
Control()->appendInst(InstCast::create(Func, InstCast::Zext, Dest, Tmp));
break;
}
case kExprI32Ctz: {
Dest = makeVariable(IceType_i32);
const auto FnName = Ctx->getGlobalString("llvm.cttz.i32");
bool BadInstrinsic = false;
const auto *Info = Ctx->getIntrinsicsInfo().find(FnName, BadInstrinsic);
assert(!BadInstrinsic);
assert(Info);
auto *Call = InstIntrinsic::create(
Func, 1, Dest, Ctx->getConstantExternSym(FnName), Info->Info);
Call->addArg(Input);
Control()->appendInst(Call);
break;
}
case kExprF32Neg: {
Dest = makeVariable(IceType_f32);
Control()->appendInst(InstArithmetic::create(
Func, InstArithmetic::Fsub, Dest, Ctx->getConstantFloat(0), Input));
break;
}
case kExprF64Neg: {
Dest = makeVariable(IceType_f64);
Control()->appendInst(InstArithmetic::create(
Func, InstArithmetic::Fsub, Dest, Ctx->getConstantDouble(0), Input));
break;
}
case kExprF32Abs: {
Dest = makeVariable(IceType_f32);
const auto FnName = Ctx->getGlobalString("llvm.fabs.f32");
bool BadInstrinsic = false;
const auto *Info = Ctx->getIntrinsicsInfo().find(FnName, BadInstrinsic);
assert(!BadInstrinsic);
assert(Info);
auto *Call = InstIntrinsic::create(
Func, 1, Dest, Ctx->getConstantExternSym(FnName), Info->Info);
Call->addArg(Input);
Control()->appendInst(Call);
break;
}
case kExprF64Abs: {
Dest = makeVariable(IceType_f64);
const auto FnName = Ctx->getGlobalString("llvm.fabs.f64");
bool BadInstrinsic = false;
const auto *Info = Ctx->getIntrinsicsInfo().find(FnName, BadInstrinsic);
assert(!BadInstrinsic);
assert(Info);
auto *Call = InstIntrinsic::create(
Func, 1, Dest, Ctx->getConstantExternSym(FnName), Info->Info);
Call->addArg(Input);
Control()->appendInst(Call);
break;
}
case kExprF32Floor: {
Dest = makeVariable(IceType_f64);
const auto FnName = Ctx->getGlobalString("env$$floor_f");
constexpr bool HasTailCall = false;
auto *Call = InstCall::create(
Func, 1, Dest, Ctx->getConstantExternSym(FnName), HasTailCall);
Call->addArg(Input);
Control()->appendInst(Call);
break;
}
case kExprF64Floor: {
Dest = makeVariable(IceType_f64);
const auto FnName = Ctx->getGlobalString("env$$floor_d");
constexpr bool HasTailCall = false;
auto *Call = InstCall::create(
Func, 1, Dest, Ctx->getConstantExternSym(FnName), HasTailCall);
Call->addArg(Input);
Control()->appendInst(Call);
break;
}
case kExprF32Sqrt: {
Dest = makeVariable(IceType_f32);
const auto FnName = Ctx->getGlobalString("llvm.sqrt.f32");
bool BadInstrinsic = false;
const auto *Info = Ctx->getIntrinsicsInfo().find(FnName, BadInstrinsic);
assert(!BadInstrinsic);
assert(Info);
auto *Call = InstIntrinsic::create(
Func, 1, Dest, Ctx->getConstantExternSym(FnName), Info->Info);
Call->addArg(Input);
Control()->appendInst(Call);
break;
}
case kExprF64Sqrt: {
Dest = makeVariable(IceType_f64);
const auto FnName = Ctx->getGlobalString("llvm.sqrt.f64");
bool BadInstrinsic = false;
const auto *Info = Ctx->getIntrinsicsInfo().find(FnName, BadInstrinsic);
assert(!BadInstrinsic);
assert(Info);
auto *Call = InstIntrinsic::create(
Func, 1, Dest, Ctx->getConstantExternSym(FnName), Info->Info);
Call->addArg(Input);
Control()->appendInst(Call);
break;
}
case kExprI64UConvertI32:
Dest = makeVariable(IceType_i64);
Control()->appendInst(
InstCast::create(Func, InstCast::Zext, Dest, Input));
break;
case kExprI64SConvertI32:
Dest = makeVariable(IceType_i64);
Control()->appendInst(
InstCast::create(Func, InstCast::Sext, Dest, Input));
break;
case kExprI32SConvertF32:
Dest = makeVariable(IceType_i32);
Control()->appendInst(
InstCast::create(Func, InstCast::Fptosi, Dest, Input));
break;
case kExprI32UConvertF32:
Dest = makeVariable(IceType_i32);
Control()->appendInst(
InstCast::create(Func, InstCast::Fptoui, Dest, Input));
break;
case kExprI32SConvertF64:
Dest = makeVariable(IceType_i32);
Control()->appendInst(
InstCast::create(Func, InstCast::Fptosi, Dest, Input));
break;
case kExprI32UConvertF64:
Dest = makeVariable(IceType_i32);
Control()->appendInst(
InstCast::create(Func, InstCast::Fptoui, Dest, Input));
break;
case kExprI32ReinterpretF32:
Dest = makeVariable(IceType_i32);
Control()->appendInst(
InstCast::create(Func, InstCast::Bitcast, Dest, Input));
break;
case kExprI64ReinterpretF64:
Dest = makeVariable(IceType_i64);
Control()->appendInst(
InstCast::create(Func, InstCast::Bitcast, Dest, Input));
break;
case kExprF64ReinterpretI64:
Dest = makeVariable(IceType_f64);
Control()->appendInst(
InstCast::create(Func, InstCast::Bitcast, Dest, Input));
break;
case kExprI32ConvertI64:
Dest = makeVariable(IceType_i32);
Control()->appendInst(
InstCast::create(Func, InstCast::Trunc, Dest, Input));
break;
case kExprF64SConvertI32:
Dest = makeVariable(IceType_f64);
Control()->appendInst(
InstCast::create(Func, InstCast::Sitofp, Dest, Input));
break;
case kExprF64UConvertI32:
Dest = makeVariable(IceType_f64);
Control()->appendInst(
InstCast::create(Func, InstCast::Uitofp, Dest, Input));
break;
case kExprF64ConvertF32:
Dest = makeVariable(IceType_f64);
Control()->appendInst(
InstCast::create(Func, InstCast::Fpext, Dest, Input));
break;
case kExprF32SConvertI32:
Dest = makeVariable(IceType_f32);
Control()->appendInst(
InstCast::create(Func, InstCast::Sitofp, Dest, Input));
break;
case kExprF32UConvertI32:
Dest = makeVariable(IceType_f32);
Control()->appendInst(
InstCast::create(Func, InstCast::Uitofp, Dest, Input));
break;
case kExprF32ReinterpretI32:
Dest = makeVariable(IceType_f32);
Control()->appendInst(
InstCast::create(Func, InstCast::Bitcast, Dest, Input));
break;
case kExprF32ConvertF64:
Dest = makeVariable(IceType_f32);
Control()->appendInst(
InstCast::create(Func, InstCast::Fptrunc, Dest, Input));
break;
default:
LOG(out << "Unknown unop: " << WasmOpcodes::OpcodeName(Opcode) << "\n");
llvm::report_fatal_error("Uncovered or invalid unop.");
return OperandNode(nullptr);
}
LOG(out << Dest << "\n");
return OperandNode(Dest);
}
uint32_t InputCount(CfgNode *Node) const { return Node->getInEdges().size(); }
bool IsPhiWithMerge(Node Phi, Node Merge) const {
LOG(out << "IsPhiWithMerge(" << Phi << ", " << Merge << ")"
<< "\n");
if (Phi && Phi.isOperand()) {
LOG(out << " ...is operand"
<< "\n");
if (getDefiningInst(Phi)) {
LOG(out << " ...has defining instruction"
<< "\n");
LOG(out << getDefNode(Phi) << "\n");
LOG(out << " ..." << (getDefNode(Phi) == Merge) << "\n");
return getDefNode(Phi) == Merge;
}
}
return false;
}
void AppendToMerge(CfgNode *Merge, CfgNode *From) const {
From->appendInst(InstBr::create(Func, Merge));
}
void AppendToPhi(Node Merge, Node Phi, Node From) {
LOG(out << "AppendToPhi(" << Merge << ", " << Phi << ", " << From << ")"
<< "\n");
auto *Inst = getDefiningInst(Phi);
assert(Inst->getDest()->getType() == From.toOperand()->getType());
Inst->addArgument(From, getDefNode(From));
}
//-----------------------------------------------------------------------
// Operations that read and/or write {control} and {effect}.
//-----------------------------------------------------------------------
Node Branch(Node Cond, Node *TrueNode, Node *FalseNode) {
// true_node and false_node appear to be out parameters.
LOG(out << "Branch(" << Cond << ", ");
// save control here because true_node appears to alias control.
auto *Ctrl = Control();
*TrueNode = OperandNode(Func->makeNode());
*FalseNode = OperandNode(Func->makeNode());
LOG(out << *TrueNode << ", " << *FalseNode << ")"
<< "\n");
auto *CondBool = Cond.toOperand()->asBoolean();
if (CondBool == nullptr) {
CondBool = makeVariable(IceType_i1);
Ctrl->appendInst(InstIcmp::create(Func, InstIcmp::Ne, CondBool, Cond,
Ctx->getConstantInt32(0)));
}
Ctrl->appendInst(InstBr::create(Func, CondBool, *TrueNode, *FalseNode));
return OperandNode(nullptr);
}
InstSwitch *CurrentSwitch = nullptr;
CfgNode *SwitchNode = nullptr;
SizeT SwitchIndex = 0;
Node Switch(uint32_t Count, Node Key) {
LOG(out << "Switch(" << Count << ", " << Key << ")\n");
assert(!CurrentSwitch);
auto *Default = Func->makeNode();
// Count - 1 because the decoder counts the default label but Subzero does
// not.
CurrentSwitch = InstSwitch::create(Func, Count - 1, Key, Default);
SwitchIndex = 0;
SwitchNode = Control();
// We don't actually append the switch to the CfgNode here because not all
// the branches are ready.
return Node(nullptr);
}
Node IfValue(int32_t Value, Node) {
LOG(out << "IfValue(" << Value << ") [Index = " << SwitchIndex << "]\n");
assert(CurrentSwitch);
auto *Target = Func->makeNode();
CurrentSwitch->addBranch(SwitchIndex++, Value, Target);
return Node(Target);
}
Node IfDefault(Node) {
LOG(out << "IfDefault(...) [Index = " << SwitchIndex << "]\n");
assert(CurrentSwitch);
assert(CurrentSwitch->getLabelDefault());
// Now we append the switch, since this should be the last edge.
assert(SwitchIndex == CurrentSwitch->getNumCases());
SwitchNode->appendInst(CurrentSwitch);
SwitchNode = nullptr;
auto Default = Node(CurrentSwitch->getLabelDefault());
CurrentSwitch = nullptr;
return Default;
}
Node Return(uint32_t Count, Node *Vals) {
assert(1 >= Count);
LOG(out << "Return(");
if (Count > 0)
LOG(out << Vals[0]);
LOG(out << ")"
<< "\n");
auto *Instr =
1 == Count ? InstRet::create(Func, Vals[0]) : InstRet::create(Func);
Control()->appendInst(Instr);
Control()->setHasReturn();
LOG(out << Node(nullptr) << "\n");
return OperandNode(nullptr);
}
Node ReturnVoid() {
LOG(out << "ReturnVoid() = ");
auto *Instr = InstRet::create(Func);
Control()->appendInst(Instr);
Control()->setHasReturn();
LOG(out << Node(nullptr) << "\n");
return OperandNode(nullptr);
}
Node Unreachable() {
LOG(out << "Unreachable() = ");
auto *Instr = InstUnreachable::create(Func);
Control()->appendInst(Instr);
LOG(out << Node(nullptr) << "\n");
return OperandNode(nullptr);
}
Node CallDirect(uint32_t Index, Node *Args) {
LOG(out << "CallDirect(" << Index << ")"
<< "\n");
assert(Module->IsValidFunction(Index));
const auto *Module = this->Module->module;
assert(Module);
const auto &Target = Module->functions[Index];
const auto *Sig = Target.sig;
assert(Sig);
const auto NumArgs = Sig->parameter_count();
LOG(out << " number of args: " << NumArgs << "\n");
const auto TargetName = getFunctionName(Module, Index);
LOG(out << " target name: " << TargetName << "\n");
assert(Sig->return_count() <= 1);
auto TargetOperand =
Ctx->getConstantSym(0, Ctx->getGlobalString(TargetName));
auto *Dest = Sig->return_count() > 0
? makeVariable(toIceType(Sig->GetReturn()))
: nullptr;
auto *Call = InstCall::create(Func, NumArgs, Dest, TargetOperand,
false /* HasTailCall */);
for (uint32_t i = 0; i < NumArgs; ++i) {
// The builder reserves the first argument for the code object.
LOG(out << " args[" << i << "] = " << Args[i + 1] << "\n");
Call->addArg(Args[i + 1]);
}
Control()->appendInst(Call);
LOG(out << "Call Result = " << Node(Dest) << "\n");
return OperandNode(Dest);
}
Node CallImport(uint32_t Index, Node *Args) {
LOG(out << "CallImport(" << Index << ")"
<< "\n");
const auto *Module = this->Module->module;
assert(Module);
const auto *Sig = this->Module->GetImportSignature(Index);
assert(Sig);
const auto NumArgs = Sig->parameter_count();
LOG(out << " number of args: " << NumArgs << "\n");
const auto &Target = Module->import_table[Index];
const auto ModuleName = toStdString(
Module->GetName(Target.module_name_offset, Target.module_name_length));
const auto FnName = toStdString(Module->GetName(
Target.function_name_offset, Target.function_name_length));
const auto TargetName = Ctx->getGlobalString(ModuleName + "$$" + FnName);
LOG(out << " target name: " << TargetName << "\n");
assert(Sig->return_count() <= 1);
auto TargetOperand = Ctx->getConstantExternSym(TargetName);
auto *Dest = Sig->return_count() > 0
? makeVariable(toIceType(Sig->GetReturn()))
: nullptr;
constexpr bool NoTailCall = false;
auto *Call =
InstCall::create(Func, NumArgs, Dest, TargetOperand, NoTailCall);
for (uint32_t i = 0; i < NumArgs; ++i) {
// The builder reserves the first argument for the code object.
LOG(out << " args[" << i << "] = " << Args[i + 1] << "\n");
assert(Args[i + 1].toOperand()->getType() == toIceType(Sig->GetParam(i)));
Call->addArg(Args[i + 1]);
}
Control()->appendInst(Call);
LOG(out << "Call Result = " << Node(Dest) << "\n");
return OperandNode(Dest);
}
Node CallIndirect(uint32_t SigIndex, Node *Args) {
LOG(out << "CallIndirect(" << SigIndex << ")\n");
// TODO(eholk): Compile to something better than a switch.
const auto *Module = this->Module->module;
assert(Module);
const auto &IndirectTable = Module->function_table;
auto *Abort = getIndirectFailTarget();
assert(Args[0].toOperand());
auto *Switch = InstSwitch::create(Func, IndirectTable.size(),
Args[0].toOperand(), Abort);
assert(Abort);
const bool HasReturn = Module->signatures[SigIndex]->return_count() != 0;
const Ice::Type DestTy =
HasReturn ? toIceType(Module->signatures[SigIndex]->GetReturn())
: IceType_void;
auto *Dest = HasReturn ? makeVariable(DestTy) : nullptr;
auto *ExitNode = Func->makeNode();
auto *PhiInst =
HasReturn ? InstPhi::create(Func, IndirectTable.size(), Dest) : nullptr;
for (uint32_t Index = 0; Index < IndirectTable.size(); ++Index) {
const auto &Target = Module->functions[IndirectTable[Index]];
if (SigIndex == Target.sig_index) {
auto *CallNode = Func->makeNode();
auto *SavedControl = Control();
*ControlPtr = OperandNode(CallNode);
auto *Tmp = CallDirect(Target.func_index, Args).toOperand();
*ControlPtr = OperandNode(SavedControl);
if (PhiInst) {
PhiInst->addArgument(Tmp, CallNode);
}
CallNode->appendInst(InstBr::create(Func, ExitNode));
Switch->addBranch(Index, Index, CallNode);
} else {
Switch->addBranch(Index, Index, Abort);
}
}
if (PhiInst) {
ExitNode->appendInst(PhiInst);
}
Control()->appendInst(Switch);
*ControlPtr = OperandNode(ExitNode);
return OperandNode(Dest);
}
Node Invert(Node Node) {
(void)Node;
llvm::report_fatal_error("Invert");
}
//-----------------------------------------------------------------------
// Operations that concern the linear memory.
//-----------------------------------------------------------------------
Node MemSize(uint32_t Offset) {
(void)Offset;
llvm::report_fatal_error("MemSize");
}
Node LoadGlobal(uint32_t Index) {
(void)Index;
llvm::report_fatal_error("LoadGlobal");
}
Node StoreGlobal(uint32_t Index, Node Val) {
(void)Index;
(void)Val;
llvm::report_fatal_error("StoreGlobal");
}
Node LoadMem(wasm::LocalType Type, MachineType MemType, Node Index,
uint32_t Offset) {
LOG(out << "LoadMem." << toIceType(MemType) << "(" << Index << "[" << Offset
<< "]) = ");
auto *RealAddr = sanitizeAddress(Index, Offset);
auto *LoadResult = makeVariable(toIceType(MemType));
Control()->appendInst(InstLoad::create(Func, LoadResult, RealAddr));
// and cast, if needed
Variable *Result = nullptr;
if (toIceType(Type) != toIceType(MemType)) {
auto DestType = toIceType(Type);
Result = makeVariable(DestType);
// TODO(eholk): handle signs correctly.
if (isScalarIntegerType(DestType)) {
if (MemType.IsSigned()) {
Control()->appendInst(
InstCast::create(Func, InstCast::Sext, Result, LoadResult));
} else {
Control()->appendInst(
InstCast::create(Func, InstCast::Zext, Result, LoadResult));
}
} else if (isScalarFloatingType(DestType)) {
Control()->appendInst(
InstCast::create(Func, InstCast::Sitofp, Result, LoadResult));
} else {
llvm::report_fatal_error("Unsupported type for memory load");
}
} else {
Result = LoadResult;
}
LOG(out << Result << "\n");
return OperandNode(Result);
}
void StoreMem(MachineType Type, Node Index, uint32_t Offset, Node Val) {
LOG(out << "StoreMem." << toIceType(Type) << "(" << Index << "[" << Offset
<< "] = " << Val << ")"
<< "\n");
auto *RealAddr = sanitizeAddress(Index, Offset);
// cast the value to the right type, if needed
Operand *StoreVal = nullptr;
if (toIceType(Type) != Val.toOperand()->getType()) {
auto *LocalStoreVal = makeVariable(toIceType(Type));
Control()->appendInst(
InstCast::create(Func, InstCast::Trunc, LocalStoreVal, Val));
StoreVal = LocalStoreVal;
} else {
StoreVal = Val;
}
// then store the memory
Control()->appendInst(InstStore::create(Func, StoreVal, RealAddr));
}
static void PrintDebugName(OperandNode Node) {
(void)Node;
llvm::report_fatal_error("PrintDebugName");
}
CfgNode *Control() {
return ControlPtr ? ControlPtr->toCfgNode() : Func->getEntryNode();
}
Node Effect() { return *EffectPtr; }
void set_module(wasm::ModuleEnv *Module) { this->Module = Module; }
void set_control_ptr(Node *Control) { this->ControlPtr = Control; }
void set_effect_ptr(Node *Effect) { this->EffectPtr = Effect; }
private:
wasm::ModuleEnv *Module;
Node *ControlPtr;
Node *EffectPtr;
class Cfg *Func;
GlobalContext *Ctx;
CfgNode *BoundsFailTarget = nullptr;
CfgNode *IndirectFailTarget = nullptr;
SizeT NextArg = 0;
CfgUnorderedMap<Operand *, InstPhi *> PhiMap;
CfgUnorderedMap<Operand *, CfgNode *> DefNodeMap;
Operand *WasmMemory = nullptr;
InstPhi *getDefiningInst(Operand *Op) const {
const auto &Iter = PhiMap.find(Op);
if (Iter == PhiMap.end()) {
return nullptr;
}
return Iter->second;
}
void setDefiningInst(Operand *Op, InstPhi *Phi) {
LOG(out << "\n== setDefiningInst(" << Op << ", " << Phi << ") ==\n");
PhiMap.emplace(Op, Phi);
}
template <typename T = Variable> T *makeVariable(Ice::Type Type) {
return makeVariable<T>(Type, Control());
}
template <typename T = Variable>
T *makeVariable(Ice::Type Type, CfgNode *DefNode) {
auto *Var = Func->makeVariable<T>(Type);
DefNodeMap.emplace(Var, DefNode);
return Var;
}
CfgNode *getDefNode(Operand *Op) const {
const auto &Iter = DefNodeMap.find(Op);
if (Iter == DefNodeMap.end()) {
return nullptr;
}
return Iter->second;
}
CfgNode *getBoundsFailTarget() {
if (!BoundsFailTarget) {
// TODO (eholk): Move this node to the end of the CFG, or even better,
// have only one abort block for the whole module.
BoundsFailTarget = Func->makeNode();
BoundsFailTarget->appendInst(InstCall::create(
Func, 0, nullptr,
Ctx->getConstantExternSym(Ctx->getGlobalString("__Sz_bounds_fail")),
false));
BoundsFailTarget->appendInst(InstUnreachable::create(Func));
}
return BoundsFailTarget;
}
CfgNode *getIndirectFailTarget() {
if (!IndirectFailTarget) {
// TODO (eholk): Move this node to the end of the CFG, or even better,
// have only one abort block for the whole module.
IndirectFailTarget = Func->makeNode();
IndirectFailTarget->appendInst(InstCall::create(
Func, 0, nullptr,
Ctx->getConstantExternSym(Ctx->getGlobalString("__Sz_indirect_fail")),
false));
IndirectFailTarget->appendInst(InstUnreachable::create(Func));
}
return IndirectFailTarget;
}
Operand *getWasmMemory() {
assert(WasmMemory != nullptr);
return WasmMemory;
}
Operand *sanitizeAddress(Operand *Base, uint32_t Offset) {
SizeT MemSize = Module->module->min_mem_pages * WASM_PAGE_SIZE;
bool ConstZeroBase = false;
// first, add the index and the offset together.
if (auto *ConstBase = llvm::dyn_cast<ConstantInteger32>(Base)) {
uint32_t RealOffset = Offset + ConstBase->getValue();
if (RealOffset >= MemSize) {
// We've proven this will always be an out of bounds access, so insert
// an unconditional trap.
Control()->appendInst(InstUnreachable::create(Func));
// It doesn't matter what we return here, so return something that will
// allow the rest of code generation to happen.
//
// We might be tempted to just abort translation here, but out of bounds
// memory access is a runtime trap, not a compile error.
return Ctx->getConstantZero(getPointerType());
}
Base = Ctx->getConstantInt32(RealOffset);
ConstZeroBase = (0 == RealOffset);
} else if (0 != Offset) {
auto *Addr = makeVariable(Ice::getPointerType());
auto *OffsetConstant = Ctx->getConstantInt32(Offset);
Control()->appendInst(InstArithmetic::create(Func, InstArithmetic::Add,
Addr, Base, OffsetConstant));
Base = Addr;
}
// Do the bounds check if enabled
if (getFlags().getWasmBoundsCheck() &&
!llvm::isa<ConstantInteger32>(Base)) {
// TODO (eholk): creating a new basic block on every memory access is
// terrible (see https://goo.gl/Zj7DTr). Try adding a new instruction that
// encapsulates this "abort if false" pattern.
auto *CheckPassed = Func->makeNode();
auto *CheckFailed = getBoundsFailTarget();
auto *Check = makeVariable(IceType_i1);
Control()->appendInst(InstIcmp::create(Func, InstIcmp::Ult, Check, Base,
Ctx->getConstantInt32(MemSize)));
Control()->appendInst(
InstBr::create(Func, Check, CheckPassed, CheckFailed));
*ControlPtr = OperandNode(CheckPassed);
}
Ice::Operand *RealAddr = nullptr;
auto MemBase = getWasmMemory();
if (!ConstZeroBase) {
auto RealAddrV = Func->makeVariable(Ice::getPointerType());
Control()->appendInst(InstArithmetic::create(Func, InstArithmetic::Add,
RealAddrV, Base, MemBase));
RealAddr = RealAddrV;
} else {
RealAddr = MemBase;
}
return RealAddr;
}
template <typename F = std::function<void(Ostream &)>> void log(F Fn) const {
if (BuildDefs::dump() && (getFlags().getVerbose() & IceV_Wasm)) {
Fn(Ctx->getStrDump());
Ctx->getStrDump().flush();
}
}
};
std::unique_ptr<Cfg> WasmTranslator::translateFunction(Zone *Zone,
FunctionBody &Body) {
OstreamLocker L1(Ctx);
auto Func = Cfg::create(Ctx, getNextSequenceNumber());
TimerMarker T(TimerStack::TT_wasmGenIce, Func.get());
Ice::CfgLocalAllocatorScope L2(Func.get());
// TODO(eholk): parse the function signature...
Func->setEntryNode(Func->makeNode());
IceBuilder Builder(Func.get());
SR_WasmDecoder<OperandNode, IceBuilder> Decoder(Zone, &Builder, Body);
LOG(out << getFlags().getDefaultGlobalPrefix() << "\n");
Decoder.Decode();
// We don't always know where the incoming branches are in phi nodes, so this
// function finds them.
Func->fixPhiNodes();
Func->computeInOutEdges();
return Func;
}
constexpr SizeT InitialBufferSize = 16 << 10; // 16KB
WasmTranslator::WasmTranslator(GlobalContext *Ctx)
: Translator(Ctx), Buffer(InitialBufferSize) {}
void WasmTranslator::translate(
const std::string &IRFilename,
std::unique_ptr<llvm::DataStreamer> InputStream) {
TimerMarker T(TimerStack::TT_wasm, Ctx);
LOG(out << "Initializing v8/wasm stuff..."
<< "\n");
Zone Zone;
ZoneScope _(&Zone);
SizeT BytesRead = 0;
while (true) {
BytesRead +=
InputStream->GetBytes(&Buffer[BytesRead], Buffer.size() - BytesRead);
LOG(out << "Read " << BytesRead << " bytes"
<< "\n");
if (BytesRead < Buffer.size())
break;
Buffer.resize(Buffer.size() * 2);
}
LOG(out << "Decoding module " << IRFilename << "\n");
constexpr v8::internal::Isolate *NoIsolate = nullptr;
auto Result = DecodeWasmModule(NoIsolate, &Zone, Buffer.data(),
Buffer.data() + BytesRead, false, kWasmOrigin);
auto Module = Result.val;
LOG(out << "Module info:"
<< "\n");
LOG(out << " min_mem_pages: " << Module->min_mem_pages << "\n");
LOG(out << " max_mem_pages: " << Module->max_mem_pages << "\n");
LOG(out << " number of globals: " << Module->globals.size() << "\n");
LOG(out << " number of signatures: " << Module->signatures.size()
<< "\n");
LOG(out << " number of functions: " << Module->functions.size() << "\n");
LOG(out << " number of data_segments: " << Module->data_segments.size()
<< "\n");
LOG(out << " function table size: " << Module->function_table.size()
<< "\n");
LOG(out << " import table size: " << Module->import_table.size()
<< "\n");
LOG(out << " export table size: " << Module->export_table.size()
<< "\n");
LOG(out << "\n"
<< "Data segment information:"
<< "\n");
uint32_t Id = 0;
for (const auto Seg : Module->data_segments) {
LOG(out << Id << ": (" << Seg.source_offset << ", " << Seg.source_size
<< ") => " << Seg.dest_addr);
if (Seg.init) {
LOG(out << " init\n");
} else {
LOG(out << "\n");
}
Id++;
}
LOG(out << "\n"
<< "Import information:"
<< "\n");
for (const auto Import : Module->import_table) {
auto ModuleName = toStdString(
Module->GetName(Import.module_name_offset, Import.module_name_length));
auto FnName = toStdString(Module->GetName(Import.function_name_offset,
Import.function_name_length));
LOG(out << " " << Import.sig_index << ": " << ModuleName << "::" << FnName
<< "\n");
}
LOG(out << "\n"
<< "Export information:"
<< "\n");
for (const auto Export : Module->export_table) {
LOG(out << " " << Export.func_index << ": "
<< toStdString(
Module->GetName(Export.name_offset, Export.name_length))
<< " (" << Export.name_offset << ", " << Export.name_length << ")");
LOG(out << "\n");
}
LOG(out << "\n"
<< "Function information:"
<< "\n");
for (const auto F : Module->functions) {
LOG(out << " " << F.func_index << ": "
<< toStdString(Module->GetName(F.name_offset, F.name_length))
<< " (" << F.name_offset << ", " << F.name_length << ")");
if (F.exported)
LOG(out << " export");
if (F.external)
LOG(out << " extern");
LOG(out << "\n");
}
LOG(out << "\n"
<< "Indirect table:"
<< "\n");
for (uint32_t F : Module->function_table) {
LOG(out << " " << F << ": " << getFunctionName(Module, F) << "\n");
}
ModuleEnv ModuleEnv;
ModuleEnv.module = Module;
FunctionBody Body;
Body.module = &ModuleEnv;
LOG(out << "Translating " << IRFilename << "\n");
{
unique_ptr<VariableDeclarationList> Globals =
makeUnique<VariableDeclarationList>();
// Global variables, etc go here.
auto *WasmMemory = VariableDeclaration::createExternal(Globals.get());
WasmMemory->setName(Ctx->getGlobalString("WASM_DATA_INIT"));
// Fill in the segments
SizeT WritePtr = 0;
for (const auto Seg : Module->data_segments) {
// fill in gaps with zero.
if (Seg.dest_addr > WritePtr) {
WasmMemory->addInitializer(VariableDeclaration::ZeroInitializer::create(
Globals.get(), Seg.dest_addr - WritePtr));
WritePtr = Seg.dest_addr;
}
// Add the data
WasmMemory->addInitializer(VariableDeclaration::DataInitializer::create(
Globals.get(),
reinterpret_cast<const char *>(Module->module_start) +
Seg.source_offset,
Seg.source_size));
WritePtr += Seg.source_size;
}
// Save the size of the initialized data in a global variable so the runtime
// can use it to determine the initial heap break.
auto *GlobalDataSize = VariableDeclaration::createExternal(Globals.get());
GlobalDataSize->setName(Ctx->getGlobalString("WASM_DATA_SIZE"));
GlobalDataSize->addInitializer(VariableDeclaration::DataInitializer::create(
Globals.get(), reinterpret_cast<const char *>(&WritePtr),
sizeof(WritePtr)));
// Save the number of pages for the runtime
auto *GlobalNumPages = VariableDeclaration::createExternal(Globals.get());
GlobalNumPages->setName(Ctx->getGlobalString("WASM_NUM_PAGES"));
GlobalNumPages->addInitializer(VariableDeclaration::DataInitializer::create(
Globals.get(), reinterpret_cast<const char *>(&Module->min_mem_pages),
sizeof(Module->min_mem_pages)));
Globals->push_back(WasmMemory);
Globals->push_back(GlobalDataSize);
Globals->push_back(GlobalNumPages);
lowerGlobals(std::move(Globals));
}
// Translate each function.
for (const auto Fn : Module->functions) {
const auto FnName = getFunctionName(Module, Fn.func_index);
LOG(out << " " << Fn.func_index << ": " << FnName << "...");
Body.sig = Fn.sig;
Body.base = Buffer.data();
Body.start = Buffer.data() + Fn.code_start_offset;
Body.end = Buffer.data() + Fn.code_end_offset;
std::unique_ptr<Cfg> Func = nullptr;
{
TimerMarker T_func(getContext(), FnName);
Func = translateFunction(&Zone, Body);
Func->setFunctionName(Ctx->getGlobalString(FnName));
}
Ctx->optQueueBlockingPush(makeUnique<CfgOptWorkItem>(std::move(Func)));
LOG(out << "done.\n");
}
return;
}
#endif // ALLOW_WASM