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/Basic/TargetInfo.h"
21#include "clang/Sema/Attr.h"
22#include "clang/Sema/Sema.h"
23
24namespace clang {
25
26SemaWasm::SemaWasm(Sema &S) : SemaBase(S) {}
27
28/// Checks the argument at the given index is a WebAssembly table and if it
29/// is, sets ElTy to the element type.
30static bool CheckWasmBuiltinArgIsTable(Sema &S, CallExpr *E, unsigned ArgIndex,
31 QualType &ElTy) {
32 Expr *ArgExpr = E->getArg(Arg: ArgIndex);
33 const auto *ATy = dyn_cast<ArrayType>(Val: ArgExpr->getType());
34 if (!ATy || !ATy->getElementType().isWebAssemblyReferenceType()) {
35 return S.Diag(Loc: ArgExpr->getBeginLoc(),
36 DiagID: diag::err_wasm_builtin_arg_must_be_table_type)
37 << ArgIndex + 1 << ArgExpr->getSourceRange();
38 }
39 ElTy = ATy->getElementType();
40 return false;
41}
42
43static bool CheckWasmTableElement(Sema &S, QualType &ElTy, CallExpr *E,
44 unsigned TableIndex, unsigned ArgIndex) {
45 Expr *NewElemArg = E->getArg(Arg: ArgIndex);
46 QualType QT = NewElemArg->getType();
47 // Compare the types after removing insignificant qualifiers
48 if (!S.getASTContext().hasSameType(T1: ElTy.getTypePtr(), T2: QT.getTypePtr())) {
49 return S.Diag(Loc: NewElemArg->getBeginLoc(),
50 DiagID: diag::err_wasm_builtin_arg_must_match_table_element_type)
51 << (ArgIndex + 1) << (TableIndex + 1)
52 << NewElemArg->getSourceRange();
53 }
54 return false;
55}
56
57/// Checks the argument at the given index is an integer.
58static bool CheckWasmBuiltinArgIsInteger(Sema &S, CallExpr *E,
59 unsigned ArgIndex) {
60 Expr *ArgExpr = E->getArg(Arg: ArgIndex);
61 if (!ArgExpr->getType()->isIntegerType()) {
62 return S.Diag(Loc: ArgExpr->getBeginLoc(),
63 DiagID: diag::err_wasm_builtin_arg_must_be_integer_type)
64 << ArgIndex + 1 << ArgExpr->getSourceRange();
65 }
66 return false;
67}
68
69bool SemaWasm::BuiltinWasmRefNullExtern(CallExpr *TheCall) {
70 if (SemaRef.checkArgCount(Call: TheCall, /*DesiredArgCount=*/0))
71 return true;
72 TheCall->setType(getASTContext().getWebAssemblyExternrefType());
73
74 return false;
75}
76
77bool SemaWasm::BuiltinWasmRefIsNullExtern(CallExpr *TheCall) {
78 if (SemaRef.checkArgCount(Call: TheCall, DesiredArgCount: 1)) {
79 return true;
80 }
81
82 Expr *ArgExpr = TheCall->getArg(Arg: 0);
83 if (!ArgExpr->getType().isWebAssemblyExternrefType()) {
84 SemaRef.Diag(Loc: ArgExpr->getBeginLoc(),
85 DiagID: diag::err_wasm_builtin_arg_must_be_externref_type)
86 << 1 << ArgExpr->getSourceRange();
87 return true;
88 }
89
90 return false;
91}
92
93bool SemaWasm::BuiltinWasmRefNullFunc(CallExpr *TheCall) {
94 ASTContext &Context = getASTContext();
95 if (SemaRef.checkArgCount(Call: TheCall, /*DesiredArgCount=*/0))
96 return true;
97
98 // This custom type checking code ensures that the nodes are as expected
99 // in order to later on generate the necessary builtin.
100 QualType Pointee = Context.getFunctionType(ResultTy: Context.VoidTy, Args: {}, EPI: {});
101 QualType Type = Context.getPointerType(T: Pointee);
102 Pointee = Context.getAddrSpaceQualType(T: Pointee, AddressSpace: LangAS::wasm_funcref);
103 Type = Context.getAttributedType(attrKind: attr::WebAssemblyFuncref, modifiedType: Type,
104 equivalentType: Context.getPointerType(T: Pointee));
105 TheCall->setType(Type);
106
107 return false;
108}
109
110/// Check that the first argument is a WebAssembly table, and the second
111/// is an index to use as index into the table.
112bool SemaWasm::BuiltinWasmTableGet(CallExpr *TheCall) {
113 if (SemaRef.checkArgCount(Call: TheCall, DesiredArgCount: 2))
114 return true;
115
116 QualType ElTy;
117 if (CheckWasmBuiltinArgIsTable(S&: SemaRef, E: TheCall, ArgIndex: 0, ElTy))
118 return true;
119
120 if (CheckWasmBuiltinArgIsInteger(S&: SemaRef, E: TheCall, ArgIndex: 1))
121 return true;
122
123 // If all is well, we set the type of TheCall to be the type of the
124 // element of the table.
125 // i.e. a table.get on an externref table has type externref,
126 // or whatever the type of the table element is.
127 TheCall->setType(ElTy);
128
129 return false;
130}
131
132/// Check that the first argument is a WebAssembly table, the second is
133/// an index to use as index into the table and the third is the reference
134/// type to set into the table.
135bool SemaWasm::BuiltinWasmTableSet(CallExpr *TheCall) {
136 if (SemaRef.checkArgCount(Call: TheCall, DesiredArgCount: 3))
137 return true;
138
139 QualType ElTy;
140 if (CheckWasmBuiltinArgIsTable(S&: SemaRef, E: TheCall, ArgIndex: 0, ElTy))
141 return true;
142
143 if (CheckWasmBuiltinArgIsInteger(S&: SemaRef, E: TheCall, ArgIndex: 1))
144 return true;
145
146 if (CheckWasmTableElement(S&: SemaRef, ElTy, E: TheCall, TableIndex: 0, ArgIndex: 2))
147 return true;
148
149 return false;
150}
151
152/// Check that the argument is a WebAssembly table.
153bool SemaWasm::BuiltinWasmTableSize(CallExpr *TheCall) {
154 if (SemaRef.checkArgCount(Call: TheCall, DesiredArgCount: 1))
155 return true;
156
157 QualType ElTy;
158 if (CheckWasmBuiltinArgIsTable(S&: SemaRef, E: TheCall, ArgIndex: 0, ElTy))
159 return true;
160
161 return false;
162}
163
164/// Check that the first argument is a WebAssembly table, the second is the
165/// value to use for new elements (of a type matching the table type), the
166/// third value is an integer.
167bool SemaWasm::BuiltinWasmTableGrow(CallExpr *TheCall) {
168 if (SemaRef.checkArgCount(Call: TheCall, DesiredArgCount: 3))
169 return true;
170
171 QualType ElTy;
172 if (CheckWasmBuiltinArgIsTable(S&: SemaRef, E: TheCall, ArgIndex: 0, ElTy))
173 return true;
174
175 if (CheckWasmTableElement(S&: SemaRef, ElTy, E: TheCall, TableIndex: 0, ArgIndex: 1))
176 return true;
177
178 if (CheckWasmBuiltinArgIsInteger(S&: SemaRef, E: TheCall, ArgIndex: 2))
179 return true;
180
181 return false;
182}
183
184/// Check that the first argument is a WebAssembly table, the second is an
185/// integer, the third is the value to use to fill the table (of a type
186/// matching the table type), and the fourth is an integer.
187bool SemaWasm::BuiltinWasmTableFill(CallExpr *TheCall) {
188 if (SemaRef.checkArgCount(Call: TheCall, DesiredArgCount: 4))
189 return true;
190
191 QualType ElTy;
192 if (CheckWasmBuiltinArgIsTable(S&: SemaRef, E: TheCall, ArgIndex: 0, ElTy))
193 return true;
194
195 if (CheckWasmBuiltinArgIsInteger(S&: SemaRef, E: TheCall, ArgIndex: 1))
196 return true;
197
198 if (CheckWasmTableElement(S&: SemaRef, ElTy, E: TheCall, TableIndex: 0, ArgIndex: 2))
199 return true;
200
201 if (CheckWasmBuiltinArgIsInteger(S&: SemaRef, E: TheCall, ArgIndex: 3))
202 return true;
203
204 return false;
205}
206
207/// Check that the first argument is a WebAssembly table, the second is also a
208/// WebAssembly table (of the same element type), and the third to fifth
209/// arguments are integers.
210bool SemaWasm::BuiltinWasmTableCopy(CallExpr *TheCall) {
211 if (SemaRef.checkArgCount(Call: TheCall, DesiredArgCount: 5))
212 return true;
213
214 QualType XElTy;
215 if (CheckWasmBuiltinArgIsTable(S&: SemaRef, E: TheCall, ArgIndex: 0, ElTy&: XElTy))
216 return true;
217
218 QualType YElTy;
219 if (CheckWasmBuiltinArgIsTable(S&: SemaRef, E: TheCall, ArgIndex: 1, ElTy&: YElTy))
220 return true;
221
222 Expr *TableYArg = TheCall->getArg(Arg: 1);
223 if (!getASTContext().hasSameType(T1: XElTy.getTypePtr(), T2: YElTy.getTypePtr())) {
224 return Diag(Loc: TableYArg->getBeginLoc(),
225 DiagID: diag::err_wasm_builtin_arg_must_match_table_element_type)
226 << 2 << 1 << TableYArg->getSourceRange();
227 }
228
229 for (int I = 2; I <= 4; I++) {
230 if (CheckWasmBuiltinArgIsInteger(S&: SemaRef, E: TheCall, ArgIndex: I))
231 return true;
232 }
233
234 return false;
235}
236
237bool SemaWasm::BuiltinWasmTestFunctionPointerSignature(const TargetInfo &TI,
238 CallExpr *TheCall) {
239 if (SemaRef.checkArgCount(Call: TheCall, DesiredArgCount: 1))
240 return true;
241
242 Expr *FuncPtrArg = TheCall->getArg(Arg: 0);
243 QualType ArgType = FuncPtrArg->getType();
244
245 // Check that the argument is a function pointer
246 const PointerType *PtrTy = ArgType->getAs<PointerType>();
247 if (!PtrTy) {
248 return Diag(Loc: FuncPtrArg->getBeginLoc(),
249 DiagID: diag::err_typecheck_expect_function_pointer)
250 << ArgType << FuncPtrArg->getSourceRange();
251 }
252
253 const FunctionProtoType *FuncTy =
254 PtrTy->getPointeeType()->getAs<FunctionProtoType>();
255 if (!FuncTy) {
256 return Diag(Loc: FuncPtrArg->getBeginLoc(),
257 DiagID: diag::err_typecheck_expect_function_pointer)
258 << ArgType << FuncPtrArg->getSourceRange();
259 }
260
261 if (TI.getABI() == "experimental-mv") {
262 auto isStructOrUnion = [](QualType T) {
263 return T->isUnionType() || T->isStructureType();
264 };
265 if (isStructOrUnion(FuncTy->getReturnType())) {
266 return Diag(
267 Loc: FuncPtrArg->getBeginLoc(),
268 DiagID: diag::
269 err_wasm_builtin_test_fp_sig_cannot_include_struct_or_union)
270 << 0 << FuncTy->getReturnType() << FuncPtrArg->getSourceRange();
271 }
272 auto NParams = FuncTy->getNumParams();
273 for (unsigned I = 0; I < NParams; I++) {
274 if (isStructOrUnion(FuncTy->getParamType(i: I))) {
275 return Diag(
276 Loc: FuncPtrArg->getBeginLoc(),
277 DiagID: diag::
278 err_wasm_builtin_test_fp_sig_cannot_include_struct_or_union)
279 << 1 << FuncPtrArg->getSourceRange();
280 }
281 }
282 }
283
284 // Set return type to int (the result of the test)
285 TheCall->setType(getASTContext().IntTy);
286 return false;
287}
288
289bool SemaWasm::CheckWebAssemblyBuiltinFunctionCall(const TargetInfo &TI,
290 unsigned BuiltinID,
291 CallExpr *TheCall) {
292 switch (BuiltinID) {
293 case WebAssembly::BI__builtin_wasm_ref_null_extern:
294 return BuiltinWasmRefNullExtern(TheCall);
295 case WebAssembly::BI__builtin_wasm_ref_null_func:
296 return BuiltinWasmRefNullFunc(TheCall);
297 case WebAssembly::BI__builtin_wasm_ref_is_null_extern:
298 return BuiltinWasmRefIsNullExtern(TheCall);
299 case WebAssembly::BI__builtin_wasm_table_get:
300 return BuiltinWasmTableGet(TheCall);
301 case WebAssembly::BI__builtin_wasm_table_set:
302 return BuiltinWasmTableSet(TheCall);
303 case WebAssembly::BI__builtin_wasm_table_size:
304 return BuiltinWasmTableSize(TheCall);
305 case WebAssembly::BI__builtin_wasm_table_grow:
306 return BuiltinWasmTableGrow(TheCall);
307 case WebAssembly::BI__builtin_wasm_table_fill:
308 return BuiltinWasmTableFill(TheCall);
309 case WebAssembly::BI__builtin_wasm_table_copy:
310 return BuiltinWasmTableCopy(TheCall);
311 case WebAssembly::BI__builtin_wasm_test_function_pointer_signature:
312 return BuiltinWasmTestFunctionPointerSignature(TI, TheCall);
313 }
314
315 return false;
316}
317
318WebAssemblyImportModuleAttr *
319SemaWasm::mergeImportModuleAttr(Decl *D,
320 const WebAssemblyImportModuleAttr &AL) {
321 auto *FD = cast<FunctionDecl>(Val: D);
322
323 if (const auto *ExistingAttr = FD->getAttr<WebAssemblyImportModuleAttr>()) {
324 if (ExistingAttr->getImportModule() == AL.getImportModule())
325 return nullptr;
326 Diag(Loc: ExistingAttr->getLocation(), DiagID: diag::warn_mismatched_import)
327 << 0 << ExistingAttr->getImportModule() << AL.getImportModule();
328 Diag(Loc: AL.getLoc(), DiagID: diag::note_previous_attribute);
329 return nullptr;
330 }
331 if (FD->hasBody()) {
332 Diag(Loc: AL.getLoc(), DiagID: diag::warn_import_on_definition) << 0;
333 return nullptr;
334 }
335 return ::new (getASTContext())
336 WebAssemblyImportModuleAttr(getASTContext(), AL, AL.getImportModule());
337}
338
339WebAssemblyImportNameAttr *
340SemaWasm::mergeImportNameAttr(Decl *D, const WebAssemblyImportNameAttr &AL) {
341 auto *FD = cast<FunctionDecl>(Val: D);
342
343 if (const auto *ExistingAttr = FD->getAttr<WebAssemblyImportNameAttr>()) {
344 if (ExistingAttr->getImportName() == AL.getImportName())
345 return nullptr;
346 Diag(Loc: ExistingAttr->getLocation(), DiagID: diag::warn_mismatched_import)
347 << 1 << ExistingAttr->getImportName() << AL.getImportName();
348 Diag(Loc: AL.getLoc(), DiagID: diag::note_previous_attribute);
349 return nullptr;
350 }
351 if (FD->hasBody()) {
352 Diag(Loc: AL.getLoc(), DiagID: diag::warn_import_on_definition) << 1;
353 return nullptr;
354 }
355 return ::new (getASTContext())
356 WebAssemblyImportNameAttr(getASTContext(), AL, AL.getImportName());
357}
358
359void SemaWasm::handleWebAssemblyImportModuleAttr(Decl *D,
360 const ParsedAttr &AL) {
361 auto *FD = cast<FunctionDecl>(Val: D);
362
363 StringRef Str;
364 SourceLocation ArgLoc;
365 if (!SemaRef.checkStringLiteralArgumentAttr(Attr: AL, ArgNum: 0, Str, ArgLocation: &ArgLoc))
366 return;
367 if (FD->hasBody()) {
368 Diag(Loc: AL.getLoc(), DiagID: diag::warn_import_on_definition) << 0;
369 return;
370 }
371
372 FD->addAttr(A: ::new (getASTContext())
373 WebAssemblyImportModuleAttr(getASTContext(), AL, Str));
374}
375
376void SemaWasm::handleWebAssemblyImportNameAttr(Decl *D, const ParsedAttr &AL) {
377 auto *FD = cast<FunctionDecl>(Val: D);
378
379 StringRef Str;
380 SourceLocation ArgLoc;
381 if (!SemaRef.checkStringLiteralArgumentAttr(Attr: AL, ArgNum: 0, Str, ArgLocation: &ArgLoc))
382 return;
383 if (FD->hasBody()) {
384 Diag(Loc: AL.getLoc(), DiagID: diag::warn_import_on_definition) << 1;
385 return;
386 }
387
388 FD->addAttr(A: ::new (getASTContext())
389 WebAssemblyImportNameAttr(getASTContext(), AL, Str));
390}
391
392void SemaWasm::handleWebAssemblyExportNameAttr(Decl *D, const ParsedAttr &AL) {
393 ASTContext &Context = getASTContext();
394 if (!isFuncOrMethodForAttrSubject(D)) {
395 Diag(Loc: D->getLocation(), DiagID: diag::warn_attribute_wrong_decl_type)
396 << AL << AL.isRegularKeywordAttribute() << ExpectedFunction;
397 return;
398 }
399
400 auto *FD = cast<FunctionDecl>(Val: D);
401 if (FD->isThisDeclarationADefinition()) {
402 Diag(Loc: D->getLocation(), DiagID: diag::err_alias_is_definition) << FD << 0;
403 return;
404 }
405
406 StringRef Str;
407 SourceLocation ArgLoc;
408 if (!SemaRef.checkStringLiteralArgumentAttr(Attr: AL, ArgNum: 0, Str, ArgLocation: &ArgLoc))
409 return;
410
411 D->addAttr(A: ::new (Context) WebAssemblyExportNameAttr(Context, AL, Str));
412 D->addAttr(A: UsedAttr::CreateImplicit(Ctx&: Context));
413}
414
415} // namespace clang
416