//===-- WebAssemblyAsmPrinter.cpp - WebAssembly LLVM assembly writer ------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// /// /// \file /// This file contains a printer that converts from our internal /// representation of machine-dependent LLVM code to the WebAssembly assembly /// language. /// //===----------------------------------------------------------------------===// #include "WebAssemblyAsmPrinter.h" #include "MCTargetDesc/WebAssemblyInstPrinter.h" #include "MCTargetDesc/WebAssemblyMCTargetDesc.h" #include "MCTargetDesc/WebAssemblyTargetStreamer.h" #include "TargetInfo/WebAssemblyTargetInfo.h" #include "WebAssembly.h" #include "WebAssemblyMCInstLower.h" #include "WebAssemblyMachineFunctionInfo.h" #include "WebAssemblyRegisterInfo.h" #include "WebAssemblyTargetMachine.h" #include "llvm/ADT/SmallSet.h" #include "llvm/ADT/StringExtras.h" #include "llvm/BinaryFormat/Wasm.h" #include "llvm/CodeGen/Analysis.h" #include "llvm/CodeGen/AsmPrinter.h" #include "llvm/CodeGen/MachineConstantPool.h" #include "llvm/CodeGen/MachineInstr.h" #include "llvm/CodeGen/MachineModuleInfoImpls.h" #include "llvm/IR/DataLayout.h" #include "llvm/IR/DebugInfoMetadata.h" #include "llvm/IR/GlobalVariable.h" #include "llvm/IR/Metadata.h" #include "llvm/MC/MCContext.h" #include "llvm/MC/MCSectionWasm.h" #include "llvm/MC/MCStreamer.h" #include "llvm/MC/MCSymbol.h" #include "llvm/MC/MCSymbolWasm.h" #include "llvm/Support/Debug.h" #include "llvm/Support/TargetRegistry.h" #include "llvm/Support/raw_ostream.h" using namespace llvm; #define DEBUG_TYPE "asm-printer" extern cl::opt WasmKeepRegisters; //===----------------------------------------------------------------------===// // Helpers. //===----------------------------------------------------------------------===// MVT WebAssemblyAsmPrinter::getRegType(unsigned RegNo) const { const TargetRegisterInfo *TRI = Subtarget->getRegisterInfo(); const TargetRegisterClass *TRC = MRI->getRegClass(RegNo); for (MVT T : {MVT::i32, MVT::i64, MVT::f32, MVT::f64, MVT::v16i8, MVT::v8i16, MVT::v4i32, MVT::v2i64, MVT::v4f32, MVT::v2f64}) if (TRI->isTypeLegalForClass(*TRC, T)) return T; LLVM_DEBUG(errs() << "Unknown type for register number: " << RegNo); llvm_unreachable("Unknown register type"); return MVT::Other; } std::string WebAssemblyAsmPrinter::regToString(const MachineOperand &MO) { Register RegNo = MO.getReg(); assert(Register::isVirtualRegister(RegNo) && "Unlowered physical register encountered during assembly printing"); assert(!MFI->isVRegStackified(RegNo)); unsigned WAReg = MFI->getWAReg(RegNo); assert(WAReg != WebAssemblyFunctionInfo::UnusedReg); return '$' + utostr(WAReg); } WebAssemblyTargetStreamer *WebAssemblyAsmPrinter::getTargetStreamer() { MCTargetStreamer *TS = OutStreamer->getTargetStreamer(); return static_cast(TS); } //===----------------------------------------------------------------------===// // WebAssemblyAsmPrinter Implementation. //===----------------------------------------------------------------------===// void WebAssemblyAsmPrinter::EmitEndOfAsmFile(Module &M) { for (auto &It : OutContext.getSymbols()) { // Emit a .globaltype and .eventtype declaration. auto Sym = cast(It.getValue()); if (Sym->getType() == wasm::WASM_SYMBOL_TYPE_GLOBAL) getTargetStreamer()->emitGlobalType(Sym); else if (Sym->getType() == wasm::WASM_SYMBOL_TYPE_EVENT) getTargetStreamer()->emitEventType(Sym); } for (const auto &F : M) { if (F.isIntrinsic()) continue; // Emit function type info for all undefined functions if (F.isDeclarationForLinker()) { SmallVector Results; SmallVector Params; computeSignatureVTs(F.getFunctionType(), F, TM, Params, Results); auto *Sym = cast(getSymbol(&F)); Sym->setType(wasm::WASM_SYMBOL_TYPE_FUNCTION); if (!Sym->getSignature()) { auto Signature = signatureFromMVTs(Results, Params); Sym->setSignature(Signature.get()); addSignature(std::move(Signature)); } // FIXME: this was originally intended for post-linking and was only used // for imports that were only called indirectly (i.e. s2wasm could not // infer the type from a call). With object files it applies to all // imports. so fix the names and the tests, or rethink how import // delcarations work in asm files. getTargetStreamer()->emitFunctionType(Sym); if (TM.getTargetTriple().isOSBinFormatWasm() && F.hasFnAttribute("wasm-import-module")) { StringRef Name = F.getFnAttribute("wasm-import-module").getValueAsString(); Sym->setImportModule(Name); getTargetStreamer()->emitImportModule(Sym, Name); } if (TM.getTargetTriple().isOSBinFormatWasm() && F.hasFnAttribute("wasm-import-name")) { StringRef Name = F.getFnAttribute("wasm-import-name").getValueAsString(); Sym->setImportName(Name); getTargetStreamer()->emitImportName(Sym, Name); } } if (F.hasFnAttribute("wasm-export-name")) { auto *Sym = cast(getSymbol(&F)); StringRef Name = F.getFnAttribute("wasm-export-name").getValueAsString(); Sym->setExportName(Name); getTargetStreamer()->emitExportName(Sym, Name); } } for (const auto &G : M.globals()) { if (!G.hasInitializer() && G.hasExternalLinkage()) { if (G.getValueType()->isSized()) { uint16_t Size = M.getDataLayout().getTypeAllocSize(G.getValueType()); OutStreamer->emitELFSize(getSymbol(&G), MCConstantExpr::create(Size, OutContext)); } } } if (const NamedMDNode *Named = M.getNamedMetadata("wasm.custom_sections")) { for (const Metadata *MD : Named->operands()) { const auto *Tuple = dyn_cast(MD); if (!Tuple || Tuple->getNumOperands() != 2) continue; const MDString *Name = dyn_cast(Tuple->getOperand(0)); const MDString *Contents = dyn_cast(Tuple->getOperand(1)); if (!Name || !Contents) continue; OutStreamer->PushSection(); std::string SectionName = (".custom_section." + Name->getString()).str(); MCSectionWasm *MySection = OutContext.getWasmSection(SectionName, SectionKind::getMetadata()); OutStreamer->SwitchSection(MySection); OutStreamer->EmitBytes(Contents->getString()); OutStreamer->PopSection(); } } EmitProducerInfo(M); EmitTargetFeatures(M); } void WebAssemblyAsmPrinter::EmitProducerInfo(Module &M) { llvm::SmallVector, 4> Languages; if (const NamedMDNode *Debug = M.getNamedMetadata("llvm.dbg.cu")) { llvm::SmallSet SeenLanguages; for (size_t I = 0, E = Debug->getNumOperands(); I < E; ++I) { const auto *CU = cast(Debug->getOperand(I)); StringRef Language = dwarf::LanguageString(CU->getSourceLanguage()); Language.consume_front("DW_LANG_"); if (SeenLanguages.insert(Language).second) Languages.emplace_back(Language.str(), ""); } } llvm::SmallVector, 4> Tools; if (const NamedMDNode *Ident = M.getNamedMetadata("llvm.ident")) { llvm::SmallSet SeenTools; for (size_t I = 0, E = Ident->getNumOperands(); I < E; ++I) { const auto *S = cast(Ident->getOperand(I)->getOperand(0)); std::pair Field = S->getString().split("version"); StringRef Name = Field.first.trim(); StringRef Version = Field.second.trim(); if (SeenTools.insert(Name).second) Tools.emplace_back(Name.str(), Version.str()); } } int FieldCount = int(!Languages.empty()) + int(!Tools.empty()); if (FieldCount != 0) { MCSectionWasm *Producers = OutContext.getWasmSection( ".custom_section.producers", SectionKind::getMetadata()); OutStreamer->PushSection(); OutStreamer->SwitchSection(Producers); OutStreamer->EmitULEB128IntValue(FieldCount); for (auto &Producers : {std::make_pair("language", &Languages), std::make_pair("processed-by", &Tools)}) { if (Producers.second->empty()) continue; OutStreamer->EmitULEB128IntValue(strlen(Producers.first)); OutStreamer->EmitBytes(Producers.first); OutStreamer->EmitULEB128IntValue(Producers.second->size()); for (auto &Producer : *Producers.second) { OutStreamer->EmitULEB128IntValue(Producer.first.size()); OutStreamer->EmitBytes(Producer.first); OutStreamer->EmitULEB128IntValue(Producer.second.size()); OutStreamer->EmitBytes(Producer.second); } } OutStreamer->PopSection(); } } void WebAssemblyAsmPrinter::EmitTargetFeatures(Module &M) { struct FeatureEntry { uint8_t Prefix; StringRef Name; }; // Read target features and linkage policies from module metadata SmallVector EmittedFeatures; for (const SubtargetFeatureKV &KV : WebAssemblyFeatureKV) { std::string MDKey = (StringRef("wasm-feature-") + KV.Key).str(); Metadata *Policy = M.getModuleFlag(MDKey); if (Policy == nullptr) continue; FeatureEntry Entry; Entry.Prefix = 0; Entry.Name = KV.Key; if (auto *MD = cast(Policy)) if (auto *I = cast(MD->getValue())) Entry.Prefix = I->getZExtValue(); // Silently ignore invalid metadata if (Entry.Prefix != wasm::WASM_FEATURE_PREFIX_USED && Entry.Prefix != wasm::WASM_FEATURE_PREFIX_REQUIRED && Entry.Prefix != wasm::WASM_FEATURE_PREFIX_DISALLOWED) continue; EmittedFeatures.push_back(Entry); } if (EmittedFeatures.size() == 0) return; // Emit features and linkage policies into the "target_features" section MCSectionWasm *FeaturesSection = OutContext.getWasmSection( ".custom_section.target_features", SectionKind::getMetadata()); OutStreamer->PushSection(); OutStreamer->SwitchSection(FeaturesSection); OutStreamer->EmitULEB128IntValue(EmittedFeatures.size()); for (auto &F : EmittedFeatures) { OutStreamer->EmitIntValue(F.Prefix, 1); OutStreamer->EmitULEB128IntValue(F.Name.size()); OutStreamer->EmitBytes(F.Name); } OutStreamer->PopSection(); } void WebAssemblyAsmPrinter::EmitConstantPool() { assert(MF->getConstantPool()->getConstants().empty() && "WebAssembly disables constant pools"); } void WebAssemblyAsmPrinter::EmitJumpTableInfo() { // Nothing to do; jump tables are incorporated into the instruction stream. } void WebAssemblyAsmPrinter::EmitFunctionBodyStart() { const Function &F = MF->getFunction(); SmallVector ResultVTs; SmallVector ParamVTs; computeSignatureVTs(F.getFunctionType(), F, TM, ParamVTs, ResultVTs); auto Signature = signatureFromMVTs(ResultVTs, ParamVTs); auto *WasmSym = cast(CurrentFnSym); WasmSym->setSignature(Signature.get()); addSignature(std::move(Signature)); WasmSym->setType(wasm::WASM_SYMBOL_TYPE_FUNCTION); // FIXME: clean up how params and results are emitted (use signatures) getTargetStreamer()->emitFunctionType(WasmSym); // Emit the function index. if (MDNode *Idx = F.getMetadata("wasm.index")) { assert(Idx->getNumOperands() == 1); getTargetStreamer()->emitIndIdx(AsmPrinter::lowerConstant( cast(Idx->getOperand(0))->getValue())); } SmallVector Locals; valTypesFromMVTs(MFI->getLocals(), Locals); getTargetStreamer()->emitLocal(Locals); AsmPrinter::EmitFunctionBodyStart(); } void WebAssemblyAsmPrinter::EmitInstruction(const MachineInstr *MI) { LLVM_DEBUG(dbgs() << "EmitInstruction: " << *MI << '\n'); switch (MI->getOpcode()) { case WebAssembly::ARGUMENT_i32: case WebAssembly::ARGUMENT_i32_S: case WebAssembly::ARGUMENT_i64: case WebAssembly::ARGUMENT_i64_S: case WebAssembly::ARGUMENT_f32: case WebAssembly::ARGUMENT_f32_S: case WebAssembly::ARGUMENT_f64: case WebAssembly::ARGUMENT_f64_S: case WebAssembly::ARGUMENT_v16i8: case WebAssembly::ARGUMENT_v16i8_S: case WebAssembly::ARGUMENT_v8i16: case WebAssembly::ARGUMENT_v8i16_S: case WebAssembly::ARGUMENT_v4i32: case WebAssembly::ARGUMENT_v4i32_S: case WebAssembly::ARGUMENT_v2i64: case WebAssembly::ARGUMENT_v2i64_S: case WebAssembly::ARGUMENT_v4f32: case WebAssembly::ARGUMENT_v4f32_S: case WebAssembly::ARGUMENT_v2f64: case WebAssembly::ARGUMENT_v2f64_S: // These represent values which are live into the function entry, so there's // no instruction to emit. break; case WebAssembly::FALLTHROUGH_RETURN: { // These instructions represent the implicit return at the end of a // function body. if (isVerbose()) { OutStreamer->AddComment("fallthrough-return"); OutStreamer->AddBlankLine(); } break; } case WebAssembly::COMPILER_FENCE: // This is a compiler barrier that prevents instruction reordering during // backend compilation, and should not be emitted. break; case WebAssembly::EXTRACT_EXCEPTION_I32: case WebAssembly::EXTRACT_EXCEPTION_I32_S: // These are pseudo instructions that simulates popping values from stack. // We print these only when we have -wasm-keep-registers on for assembly // readability. if (!WasmKeepRegisters) break; LLVM_FALLTHROUGH; default: { WebAssemblyMCInstLower MCInstLowering(OutContext, *this); MCInst TmpInst; MCInstLowering.lower(MI, TmpInst); EmitToStreamer(*OutStreamer, TmpInst); break; } } } bool WebAssemblyAsmPrinter::PrintAsmOperand(const MachineInstr *MI, unsigned OpNo, const char *ExtraCode, raw_ostream &OS) { // First try the generic code, which knows about modifiers like 'c' and 'n'. if (!AsmPrinter::PrintAsmOperand(MI, OpNo, ExtraCode, OS)) return false; if (!ExtraCode) { const MachineOperand &MO = MI->getOperand(OpNo); switch (MO.getType()) { case MachineOperand::MO_Immediate: OS << MO.getImm(); return false; case MachineOperand::MO_Register: // FIXME: only opcode that still contains registers, as required by // MachineInstr::getDebugVariable(). assert(MI->getOpcode() == WebAssembly::INLINEASM); OS << regToString(MO); return false; case MachineOperand::MO_GlobalAddress: PrintSymbolOperand(MO, OS); return false; case MachineOperand::MO_ExternalSymbol: GetExternalSymbolSymbol(MO.getSymbolName())->print(OS, MAI); printOffset(MO.getOffset(), OS); return false; case MachineOperand::MO_MachineBasicBlock: MO.getMBB()->getSymbol()->print(OS, MAI); return false; default: break; } } return true; } bool WebAssemblyAsmPrinter::PrintAsmMemoryOperand(const MachineInstr *MI, unsigned OpNo, const char *ExtraCode, raw_ostream &OS) { // The current approach to inline asm is that "r" constraints are expressed // as local indices, rather than values on the operand stack. This simplifies // using "r" as it eliminates the need to push and pop the values in a // particular order, however it also makes it impossible to have an "m" // constraint. So we don't support it. return AsmPrinter::PrintAsmMemoryOperand(MI, OpNo, ExtraCode, OS); } // Force static initialization. extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializeWebAssemblyAsmPrinter() { RegisterAsmPrinter X(getTheWebAssemblyTarget32()); RegisterAsmPrinter Y(getTheWebAssemblyTarget64()); }