xref: /freebsd/contrib/llvm-project/clang/lib/Sema/SemaWasm.cpp (revision 0fca6ea1d4eea4c934cfff25ac9ee8ad6fe95583)
1 //===------ SemaWasm.cpp ---- WebAssembly target-specific routines --------===//
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 //  This file implements semantic analysis functions specific to WebAssembly.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "clang/Sema/SemaWasm.h"
14 #include "clang/AST/ASTContext.h"
15 #include "clang/AST/Decl.h"
16 #include "clang/AST/Type.h"
17 #include "clang/Basic/AddressSpaces.h"
18 #include "clang/Basic/DiagnosticSema.h"
19 #include "clang/Basic/TargetBuiltins.h"
20 #include "clang/Sema/Attr.h"
21 #include "clang/Sema/Sema.h"
22 
23 namespace clang {
24 
SemaWasm(Sema & S)25 SemaWasm::SemaWasm(Sema &S) : SemaBase(S) {}
26 
27 /// Checks the argument at the given index is a WebAssembly table and if it
28 /// is, sets ElTy to the element type.
CheckWasmBuiltinArgIsTable(Sema & S,CallExpr * E,unsigned ArgIndex,QualType & ElTy)29 static bool CheckWasmBuiltinArgIsTable(Sema &S, CallExpr *E, unsigned ArgIndex,
30                                        QualType &ElTy) {
31   Expr *ArgExpr = E->getArg(ArgIndex);
32   const auto *ATy = dyn_cast<ArrayType>(ArgExpr->getType());
33   if (!ATy || !ATy->getElementType().isWebAssemblyReferenceType()) {
34     return S.Diag(ArgExpr->getBeginLoc(),
35                   diag::err_wasm_builtin_arg_must_be_table_type)
36            << ArgIndex + 1 << ArgExpr->getSourceRange();
37   }
38   ElTy = ATy->getElementType();
39   return false;
40 }
41 
42 /// Checks the argument at the given index is an integer.
CheckWasmBuiltinArgIsInteger(Sema & S,CallExpr * E,unsigned ArgIndex)43 static bool CheckWasmBuiltinArgIsInteger(Sema &S, CallExpr *E,
44                                          unsigned ArgIndex) {
45   Expr *ArgExpr = E->getArg(ArgIndex);
46   if (!ArgExpr->getType()->isIntegerType()) {
47     return S.Diag(ArgExpr->getBeginLoc(),
48                   diag::err_wasm_builtin_arg_must_be_integer_type)
49            << ArgIndex + 1 << ArgExpr->getSourceRange();
50   }
51   return false;
52 }
53 
BuiltinWasmRefNullExtern(CallExpr * TheCall)54 bool SemaWasm::BuiltinWasmRefNullExtern(CallExpr *TheCall) {
55   if (TheCall->getNumArgs() != 0)
56     return true;
57 
58   TheCall->setType(getASTContext().getWebAssemblyExternrefType());
59 
60   return false;
61 }
62 
BuiltinWasmRefNullFunc(CallExpr * TheCall)63 bool SemaWasm::BuiltinWasmRefNullFunc(CallExpr *TheCall) {
64   ASTContext &Context = getASTContext();
65   if (TheCall->getNumArgs() != 0) {
66     Diag(TheCall->getBeginLoc(), diag::err_typecheck_call_too_many_args)
67         << 0 /*function call*/ << /*expected*/ 0 << TheCall->getNumArgs()
68         << /*is non object*/ 0;
69     return true;
70   }
71 
72   // This custom type checking code ensures that the nodes are as expected
73   // in order to later on generate the necessary builtin.
74   QualType Pointee = Context.getFunctionType(Context.VoidTy, {}, {});
75   QualType Type = Context.getPointerType(Pointee);
76   Pointee = Context.getAddrSpaceQualType(Pointee, LangAS::wasm_funcref);
77   Type = Context.getAttributedType(attr::WebAssemblyFuncref, Type,
78                                    Context.getPointerType(Pointee));
79   TheCall->setType(Type);
80 
81   return false;
82 }
83 
84 /// Check that the first argument is a WebAssembly table, and the second
85 /// is an index to use as index into the table.
BuiltinWasmTableGet(CallExpr * TheCall)86 bool SemaWasm::BuiltinWasmTableGet(CallExpr *TheCall) {
87   if (SemaRef.checkArgCount(TheCall, 2))
88     return true;
89 
90   QualType ElTy;
91   if (CheckWasmBuiltinArgIsTable(SemaRef, TheCall, 0, ElTy))
92     return true;
93 
94   if (CheckWasmBuiltinArgIsInteger(SemaRef, TheCall, 1))
95     return true;
96 
97   // If all is well, we set the type of TheCall to be the type of the
98   // element of the table.
99   // i.e. a table.get on an externref table has type externref,
100   // or whatever the type of the table element is.
101   TheCall->setType(ElTy);
102 
103   return false;
104 }
105 
106 /// Check that the first argumnet is a WebAssembly table, the second is
107 /// an index to use as index into the table and the third is the reference
108 /// type to set into the table.
BuiltinWasmTableSet(CallExpr * TheCall)109 bool SemaWasm::BuiltinWasmTableSet(CallExpr *TheCall) {
110   if (SemaRef.checkArgCount(TheCall, 3))
111     return true;
112 
113   QualType ElTy;
114   if (CheckWasmBuiltinArgIsTable(SemaRef, TheCall, 0, ElTy))
115     return true;
116 
117   if (CheckWasmBuiltinArgIsInteger(SemaRef, TheCall, 1))
118     return true;
119 
120   if (!getASTContext().hasSameType(ElTy, TheCall->getArg(2)->getType()))
121     return true;
122 
123   return false;
124 }
125 
126 /// Check that the argument is a WebAssembly table.
BuiltinWasmTableSize(CallExpr * TheCall)127 bool SemaWasm::BuiltinWasmTableSize(CallExpr *TheCall) {
128   if (SemaRef.checkArgCount(TheCall, 1))
129     return true;
130 
131   QualType ElTy;
132   if (CheckWasmBuiltinArgIsTable(SemaRef, TheCall, 0, ElTy))
133     return true;
134 
135   return false;
136 }
137 
138 /// Check that the first argument is a WebAssembly table, the second is the
139 /// value to use for new elements (of a type matching the table type), the
140 /// third value is an integer.
BuiltinWasmTableGrow(CallExpr * TheCall)141 bool SemaWasm::BuiltinWasmTableGrow(CallExpr *TheCall) {
142   if (SemaRef.checkArgCount(TheCall, 3))
143     return true;
144 
145   QualType ElTy;
146   if (CheckWasmBuiltinArgIsTable(SemaRef, TheCall, 0, ElTy))
147     return true;
148 
149   Expr *NewElemArg = TheCall->getArg(1);
150   if (!getASTContext().hasSameType(ElTy, NewElemArg->getType())) {
151     return Diag(NewElemArg->getBeginLoc(),
152                 diag::err_wasm_builtin_arg_must_match_table_element_type)
153            << 2 << 1 << NewElemArg->getSourceRange();
154   }
155 
156   if (CheckWasmBuiltinArgIsInteger(SemaRef, TheCall, 2))
157     return true;
158 
159   return false;
160 }
161 
162 /// Check that the first argument is a WebAssembly table, the second is an
163 /// integer, the third is the value to use to fill the table (of a type
164 /// matching the table type), and the fourth is an integer.
BuiltinWasmTableFill(CallExpr * TheCall)165 bool SemaWasm::BuiltinWasmTableFill(CallExpr *TheCall) {
166   if (SemaRef.checkArgCount(TheCall, 4))
167     return true;
168 
169   QualType ElTy;
170   if (CheckWasmBuiltinArgIsTable(SemaRef, TheCall, 0, ElTy))
171     return true;
172 
173   if (CheckWasmBuiltinArgIsInteger(SemaRef, TheCall, 1))
174     return true;
175 
176   Expr *NewElemArg = TheCall->getArg(2);
177   if (!getASTContext().hasSameType(ElTy, NewElemArg->getType())) {
178     return Diag(NewElemArg->getBeginLoc(),
179                 diag::err_wasm_builtin_arg_must_match_table_element_type)
180            << 3 << 1 << NewElemArg->getSourceRange();
181   }
182 
183   if (CheckWasmBuiltinArgIsInteger(SemaRef, TheCall, 3))
184     return true;
185 
186   return false;
187 }
188 
189 /// Check that the first argument is a WebAssembly table, the second is also a
190 /// WebAssembly table (of the same element type), and the third to fifth
191 /// arguments are integers.
BuiltinWasmTableCopy(CallExpr * TheCall)192 bool SemaWasm::BuiltinWasmTableCopy(CallExpr *TheCall) {
193   if (SemaRef.checkArgCount(TheCall, 5))
194     return true;
195 
196   QualType XElTy;
197   if (CheckWasmBuiltinArgIsTable(SemaRef, TheCall, 0, XElTy))
198     return true;
199 
200   QualType YElTy;
201   if (CheckWasmBuiltinArgIsTable(SemaRef, TheCall, 1, YElTy))
202     return true;
203 
204   Expr *TableYArg = TheCall->getArg(1);
205   if (!getASTContext().hasSameType(XElTy, YElTy)) {
206     return Diag(TableYArg->getBeginLoc(),
207                 diag::err_wasm_builtin_arg_must_match_table_element_type)
208            << 2 << 1 << TableYArg->getSourceRange();
209   }
210 
211   for (int I = 2; I <= 4; I++) {
212     if (CheckWasmBuiltinArgIsInteger(SemaRef, TheCall, I))
213       return true;
214   }
215 
216   return false;
217 }
218 
CheckWebAssemblyBuiltinFunctionCall(const TargetInfo & TI,unsigned BuiltinID,CallExpr * TheCall)219 bool SemaWasm::CheckWebAssemblyBuiltinFunctionCall(const TargetInfo &TI,
220                                                    unsigned BuiltinID,
221                                                    CallExpr *TheCall) {
222   switch (BuiltinID) {
223   case WebAssembly::BI__builtin_wasm_ref_null_extern:
224     return BuiltinWasmRefNullExtern(TheCall);
225   case WebAssembly::BI__builtin_wasm_ref_null_func:
226     return BuiltinWasmRefNullFunc(TheCall);
227   case WebAssembly::BI__builtin_wasm_table_get:
228     return BuiltinWasmTableGet(TheCall);
229   case WebAssembly::BI__builtin_wasm_table_set:
230     return BuiltinWasmTableSet(TheCall);
231   case WebAssembly::BI__builtin_wasm_table_size:
232     return BuiltinWasmTableSize(TheCall);
233   case WebAssembly::BI__builtin_wasm_table_grow:
234     return BuiltinWasmTableGrow(TheCall);
235   case WebAssembly::BI__builtin_wasm_table_fill:
236     return BuiltinWasmTableFill(TheCall);
237   case WebAssembly::BI__builtin_wasm_table_copy:
238     return BuiltinWasmTableCopy(TheCall);
239   }
240 
241   return false;
242 }
243 
244 WebAssemblyImportModuleAttr *
mergeImportModuleAttr(Decl * D,const WebAssemblyImportModuleAttr & AL)245 SemaWasm::mergeImportModuleAttr(Decl *D,
246                                 const WebAssemblyImportModuleAttr &AL) {
247   auto *FD = cast<FunctionDecl>(D);
248 
249   if (const auto *ExistingAttr = FD->getAttr<WebAssemblyImportModuleAttr>()) {
250     if (ExistingAttr->getImportModule() == AL.getImportModule())
251       return nullptr;
252     Diag(ExistingAttr->getLocation(), diag::warn_mismatched_import)
253         << 0 << ExistingAttr->getImportModule() << AL.getImportModule();
254     Diag(AL.getLoc(), diag::note_previous_attribute);
255     return nullptr;
256   }
257   if (FD->hasBody()) {
258     Diag(AL.getLoc(), diag::warn_import_on_definition) << 0;
259     return nullptr;
260   }
261   return ::new (getASTContext())
262       WebAssemblyImportModuleAttr(getASTContext(), AL, AL.getImportModule());
263 }
264 
265 WebAssemblyImportNameAttr *
mergeImportNameAttr(Decl * D,const WebAssemblyImportNameAttr & AL)266 SemaWasm::mergeImportNameAttr(Decl *D, const WebAssemblyImportNameAttr &AL) {
267   auto *FD = cast<FunctionDecl>(D);
268 
269   if (const auto *ExistingAttr = FD->getAttr<WebAssemblyImportNameAttr>()) {
270     if (ExistingAttr->getImportName() == AL.getImportName())
271       return nullptr;
272     Diag(ExistingAttr->getLocation(), diag::warn_mismatched_import)
273         << 1 << ExistingAttr->getImportName() << AL.getImportName();
274     Diag(AL.getLoc(), diag::note_previous_attribute);
275     return nullptr;
276   }
277   if (FD->hasBody()) {
278     Diag(AL.getLoc(), diag::warn_import_on_definition) << 1;
279     return nullptr;
280   }
281   return ::new (getASTContext())
282       WebAssemblyImportNameAttr(getASTContext(), AL, AL.getImportName());
283 }
284 
handleWebAssemblyImportModuleAttr(Decl * D,const ParsedAttr & AL)285 void SemaWasm::handleWebAssemblyImportModuleAttr(Decl *D,
286                                                  const ParsedAttr &AL) {
287   auto *FD = cast<FunctionDecl>(D);
288 
289   StringRef Str;
290   SourceLocation ArgLoc;
291   if (!SemaRef.checkStringLiteralArgumentAttr(AL, 0, Str, &ArgLoc))
292     return;
293   if (FD->hasBody()) {
294     Diag(AL.getLoc(), diag::warn_import_on_definition) << 0;
295     return;
296   }
297 
298   FD->addAttr(::new (getASTContext())
299                   WebAssemblyImportModuleAttr(getASTContext(), AL, Str));
300 }
301 
handleWebAssemblyImportNameAttr(Decl * D,const ParsedAttr & AL)302 void SemaWasm::handleWebAssemblyImportNameAttr(Decl *D, const ParsedAttr &AL) {
303   auto *FD = cast<FunctionDecl>(D);
304 
305   StringRef Str;
306   SourceLocation ArgLoc;
307   if (!SemaRef.checkStringLiteralArgumentAttr(AL, 0, Str, &ArgLoc))
308     return;
309   if (FD->hasBody()) {
310     Diag(AL.getLoc(), diag::warn_import_on_definition) << 1;
311     return;
312   }
313 
314   FD->addAttr(::new (getASTContext())
315                   WebAssemblyImportNameAttr(getASTContext(), AL, Str));
316 }
317 
handleWebAssemblyExportNameAttr(Decl * D,const ParsedAttr & AL)318 void SemaWasm::handleWebAssemblyExportNameAttr(Decl *D, const ParsedAttr &AL) {
319   ASTContext &Context = getASTContext();
320   if (!isFuncOrMethodForAttrSubject(D)) {
321     Diag(D->getLocation(), diag::warn_attribute_wrong_decl_type)
322         << AL << AL.isRegularKeywordAttribute() << ExpectedFunction;
323     return;
324   }
325 
326   auto *FD = cast<FunctionDecl>(D);
327   if (FD->isThisDeclarationADefinition()) {
328     Diag(D->getLocation(), diag::err_alias_is_definition) << FD << 0;
329     return;
330   }
331 
332   StringRef Str;
333   SourceLocation ArgLoc;
334   if (!SemaRef.checkStringLiteralArgumentAttr(AL, 0, Str, &ArgLoc))
335     return;
336 
337   D->addAttr(::new (Context) WebAssemblyExportNameAttr(Context, AL, Str));
338   D->addAttr(UsedAttr::CreateImplicit(Context));
339 }
340 
341 } // namespace clang
342