| //===- 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 = InstIntrinsicCall::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 = InstIntrinsicCall::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 = InstIntrinsicCall::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 = InstIntrinsicCall::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 = InstIntrinsicCall::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 |