1//===-- WebAssemblyAddMissingPrototypes.cpp - Fix prototypeless functions -===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8///
9/// \file
10/// Add prototypes to prototypes-less functions.
11///
12/// WebAssembly has strict function prototype checking so we need functions
13/// declarations to match the call sites. Clang treats prototype-less functions
14/// as varargs (foo(...)) which happens to work on existing platforms but
15/// doesn't under WebAssembly. This pass will find all the call sites of each
16/// prototype-less function, ensure they agree, and then set the signature
17/// on the function declaration accordingly.
18///
19//===----------------------------------------------------------------------===//
20
21#include "WebAssembly.h"
22#include "llvm/IR/Analysis.h"
23#include "llvm/IR/Constants.h"
24#include "llvm/IR/Module.h"
25#include "llvm/IR/Operator.h"
26#include "llvm/IR/PassManager.h"
27#include "llvm/Pass.h"
28#include "llvm/Support/Debug.h"
29#include "llvm/Transforms/Utils/Local.h"
30#include "llvm/Transforms/Utils/ModuleUtils.h"
31using namespace llvm;
32
33#define DEBUG_TYPE "wasm-add-missing-prototypes"
34
35namespace {
36class WebAssemblyAddMissingPrototypesLegacy final : public ModulePass {
37 StringRef getPassName() const override {
38 return "Add prototypes to prototypes-less functions";
39 }
40
41 void getAnalysisUsage(AnalysisUsage &AU) const override {
42 AU.setPreservesCFG();
43 ModulePass::getAnalysisUsage(AU);
44 }
45
46 bool runOnModule(Module &M) override;
47
48public:
49 static char ID;
50 WebAssemblyAddMissingPrototypesLegacy() : ModulePass(ID) {}
51};
52} // End anonymous namespace
53
54char WebAssemblyAddMissingPrototypesLegacy::ID = 0;
55INITIALIZE_PASS(WebAssemblyAddMissingPrototypesLegacy, DEBUG_TYPE,
56 "Add prototypes to prototypes-less functions", false, false)
57
58ModulePass *llvm::createWebAssemblyAddMissingPrototypesLegacyPass() {
59 return new WebAssemblyAddMissingPrototypesLegacy();
60}
61
62static bool addMissingPrototypes(Module &M) {
63 LLVM_DEBUG(dbgs() << "********** Add Missing Prototypes **********\n");
64
65 std::vector<std::pair<Function *, Function *>> Replacements;
66
67 // Find all the prototype-less function declarations
68 for (Function &F : M) {
69 if (!F.isDeclaration() || !F.hasFnAttribute(Kind: "no-prototype"))
70 continue;
71
72 LLVM_DEBUG(dbgs() << "Found no-prototype function: " << F.getName()
73 << "\n");
74
75 // When clang emits prototype-less C functions it uses (...), i.e. varargs
76 // function that take no arguments (have no sentinel). When we see a
77 // no-prototype attribute we expect the function have these properties.
78 if (!F.isVarArg())
79 report_fatal_error(
80 reason: "Functions with 'no-prototype' attribute must take varargs: " +
81 F.getName());
82 unsigned NumParams = F.getFunctionType()->getNumParams();
83 if (NumParams != 0) {
84 if (!(NumParams == 1 && F.arg_begin()->hasStructRetAttr()))
85 report_fatal_error(reason: "Functions with 'no-prototype' attribute should "
86 "not have params: " +
87 F.getName());
88 }
89
90 // Find calls of this function, looking through bitcasts.
91 SmallVector<CallBase *> Calls;
92 SmallVector<Value *> Worklist;
93 Worklist.push_back(Elt: &F);
94 while (!Worklist.empty()) {
95 Value *V = Worklist.pop_back_val();
96 for (User *U : V->users()) {
97 if (auto *BC = dyn_cast<BitCastOperator>(Val: U))
98 Worklist.push_back(Elt: BC);
99 else if (auto *CB = dyn_cast<CallBase>(Val: U))
100 if (CB->getCalledOperand() == V)
101 Calls.push_back(Elt: CB);
102 }
103 }
104
105 // Create a function prototype based on the first call site that we find.
106 FunctionType *NewType = nullptr;
107 for (CallBase *CB : Calls) {
108 LLVM_DEBUG(dbgs() << "prototype-less call of " << F.getName() << ":\n");
109 LLVM_DEBUG(dbgs() << *CB << "\n");
110 FunctionType *DestType = CB->getFunctionType();
111 if (!NewType) {
112 // Create a new function with the correct type
113 NewType = DestType;
114 LLVM_DEBUG(dbgs() << "found function type: " << *NewType << "\n");
115 } else if (NewType != DestType) {
116 errs() << "warning: prototype-less function used with "
117 "conflicting signatures: "
118 << F.getName() << "\n";
119 LLVM_DEBUG(dbgs() << " " << *DestType << "\n");
120 LLVM_DEBUG(dbgs() << " " << *NewType << "\n");
121 }
122 }
123
124 if (!NewType) {
125 LLVM_DEBUG(
126 dbgs() << "could not derive a function prototype from usage: " +
127 F.getName() + "\n");
128 // We could not derive a type for this function. In this case strip
129 // the isVarArg and make it a simple zero-arg function. This has more
130 // chance of being correct. The current signature of (...) is illegal in
131 // C since it doesn't have any arguments before the "...", we this at
132 // least makes it possible for this symbol to be resolved by the linker.
133 NewType = FunctionType::get(Result: F.getFunctionType()->getReturnType(), isVarArg: false);
134 }
135
136 Function *NewF =
137 Function::Create(Ty: NewType, Linkage: F.getLinkage(), N: F.getName() + ".fixed_sig");
138 NewF->setAttributes(F.getAttributes());
139 NewF->removeFnAttr(Kind: "no-prototype");
140 Replacements.emplace_back(args: &F, args&: NewF);
141 }
142
143 for (auto &Pair : Replacements) {
144 Function *OldF = Pair.first;
145 Function *NewF = Pair.second;
146 std::string Name = std::string(OldF->getName());
147 M.getFunctionList().push_back(val: NewF);
148 OldF->replaceAllUsesWith(
149 V: ConstantExpr::getPointerBitCastOrAddrSpaceCast(C: NewF, Ty: OldF->getType()));
150 OldF->eraseFromParent();
151 NewF->setName(Name);
152 }
153
154 return !Replacements.empty();
155}
156
157bool WebAssemblyAddMissingPrototypesLegacy::runOnModule(Module &M) {
158 return addMissingPrototypes(M);
159}
160
161PreservedAnalyses
162WebAssemblyAddMissingPrototypesPass::run(Module &M,
163 ModuleAnalysisManager &MAM) {
164 return addMissingPrototypes(M)
165 ? PreservedAnalyses::none().preserveSet<CFGAnalyses>()
166 : PreservedAnalyses::all();
167}
168