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 if (auto *FD = dyn_cast<FunctionDecl>(Val: D)) {
322 if (const auto *ExistingAttr = FD->getAttr<WebAssemblyImportModuleAttr>()) {
323 if (ExistingAttr->getImportModule() == AL.getImportModule())
324 return nullptr;
325 Diag(Loc: ExistingAttr->getLocation(), DiagID: diag::warn_mismatched_import)
326 << 0 << ExistingAttr->getImportModule() << AL.getImportModule();
327 Diag(Loc: AL.getLoc(), DiagID: diag::note_previous_attribute);
328 return nullptr;
329 }
330 if (FD->hasBody()) {
331 Diag(Loc: AL.getLoc(), DiagID: diag::warn_import_on_definition) << 0 << 0;
332 return nullptr;
333 }
334 } else if (auto *VD = dyn_cast<VarDecl>(Val: D)) {
335 if (const auto *ExistingAttr = VD->getAttr<WebAssemblyImportModuleAttr>()) {
336 if (ExistingAttr->getImportModule() == AL.getImportModule())
337 return nullptr;
338 Diag(Loc: ExistingAttr->getLocation(), DiagID: diag::warn_mismatched_import)
339 << 0 << ExistingAttr->getImportModule() << AL.getImportModule();
340 Diag(Loc: AL.getLoc(), DiagID: diag::note_previous_attribute);
341 return nullptr;
342 }
343 if (VD->isThisDeclarationADefinition() != VarDecl::DeclarationOnly) {
344 Diag(Loc: AL.getLoc(), DiagID: diag::warn_import_on_definition) << 0 << 1;
345 return nullptr;
346 }
347 }
348 return ::new (getASTContext())
349 WebAssemblyImportModuleAttr(getASTContext(), AL, AL.getImportModule());
350}
351
352WebAssemblyImportNameAttr *
353SemaWasm::mergeImportNameAttr(Decl *D, const WebAssemblyImportNameAttr &AL) {
354 if (auto *FD = dyn_cast<FunctionDecl>(Val: D)) {
355 if (const auto *ExistingAttr = FD->getAttr<WebAssemblyImportNameAttr>()) {
356 if (ExistingAttr->getImportName() == AL.getImportName())
357 return nullptr;
358 Diag(Loc: ExistingAttr->getLocation(), DiagID: diag::warn_mismatched_import)
359 << 1 << ExistingAttr->getImportName() << AL.getImportName();
360 Diag(Loc: AL.getLoc(), DiagID: diag::note_previous_attribute);
361 return nullptr;
362 }
363 if (FD->hasBody()) {
364 Diag(Loc: AL.getLoc(), DiagID: diag::warn_import_on_definition) << 1 << 0;
365 return nullptr;
366 }
367 } else if (auto *VD = dyn_cast<VarDecl>(Val: D)) {
368 if (const auto *ExistingAttr = VD->getAttr<WebAssemblyImportNameAttr>()) {
369 if (ExistingAttr->getImportName() == AL.getImportName())
370 return nullptr;
371 Diag(Loc: ExistingAttr->getLocation(), DiagID: diag::warn_mismatched_import)
372 << 1 << ExistingAttr->getImportName() << AL.getImportName();
373 Diag(Loc: AL.getLoc(), DiagID: diag::note_previous_attribute);
374 return nullptr;
375 }
376 if (VD->isThisDeclarationADefinition() != VarDecl::DeclarationOnly) {
377 Diag(Loc: AL.getLoc(), DiagID: diag::warn_import_on_definition) << 1 << 1;
378 return nullptr;
379 }
380 }
381 return ::new (getASTContext())
382 WebAssemblyImportNameAttr(getASTContext(), AL, AL.getImportName());
383}
384
385WebAssemblyExportNameAttr *
386SemaWasm::mergeExportNameAttr(Decl *D, const WebAssemblyExportNameAttr &AL) {
387 if (const auto *ExistingAttr = D->getAttr<WebAssemblyExportNameAttr>()) {
388 if (ExistingAttr->getExportName() == AL.getExportName())
389 return nullptr;
390 Diag(Loc: ExistingAttr->getLocation(), DiagID: diag::warn_mismatched_import)
391 << 2 << ExistingAttr->getExportName() << AL.getExportName();
392 Diag(Loc: AL.getLoc(), DiagID: diag::note_previous_attribute);
393 return nullptr;
394 }
395 return ::new (getASTContext())
396 WebAssemblyExportNameAttr(getASTContext(), AL, AL.getExportName());
397}
398
399void SemaWasm::handleWebAssemblyImportModuleAttr(Decl *D,
400 const ParsedAttr &AL) {
401 StringRef Str;
402 SourceLocation ArgLoc;
403 if (!SemaRef.checkStringLiteralArgumentAttr(Attr: AL, ArgNum: 0, Str, ArgLocation: &ArgLoc))
404 return;
405
406 if (auto *FD = dyn_cast<FunctionDecl>(Val: D)) {
407 if (FD->hasBody()) {
408 Diag(Loc: AL.getLoc(), DiagID: diag::warn_import_on_definition) << 0 << 0;
409 return;
410 }
411 FD->addAttr(A: ::new (getASTContext())
412 WebAssemblyImportModuleAttr(getASTContext(), AL, Str));
413 } else {
414 auto *VD = cast<VarDecl>(Val: D);
415 if (VD->isThisDeclarationADefinition() != VarDecl::DeclarationOnly) {
416 Diag(Loc: AL.getLoc(), DiagID: diag::warn_import_on_definition) << 0 << 1;
417 return;
418 }
419 VD->addAttr(A: ::new (getASTContext())
420 WebAssemblyImportModuleAttr(getASTContext(), AL, Str));
421 }
422}
423
424void SemaWasm::handleWebAssemblyImportNameAttr(Decl *D, const ParsedAttr &AL) {
425 StringRef Str;
426 SourceLocation ArgLoc;
427 if (!SemaRef.checkStringLiteralArgumentAttr(Attr: AL, ArgNum: 0, Str, ArgLocation: &ArgLoc))
428 return;
429
430 if (auto *FD = dyn_cast<FunctionDecl>(Val: D)) {
431 if (FD->hasBody()) {
432 Diag(Loc: AL.getLoc(), DiagID: diag::warn_import_on_definition) << 1 << 0;
433 return;
434 }
435 FD->addAttr(A: ::new (getASTContext())
436 WebAssemblyImportNameAttr(getASTContext(), AL, Str));
437 } else {
438 auto *VD = cast<VarDecl>(Val: D);
439 if (VD->isThisDeclarationADefinition() != VarDecl::DeclarationOnly) {
440 Diag(Loc: AL.getLoc(), DiagID: diag::warn_import_on_definition) << 1 << 1;
441 return;
442 }
443 VD->addAttr(A: ::new (getASTContext())
444 WebAssemblyImportNameAttr(getASTContext(), AL, Str));
445 }
446}
447
448void SemaWasm::handleWebAssemblyExportNameAttr(Decl *D, const ParsedAttr &AL) {
449 ASTContext &Context = getASTContext();
450
451 StringRef Str;
452 SourceLocation ArgLoc;
453 if (!SemaRef.checkStringLiteralArgumentAttr(Attr: AL, ArgNum: 0, Str, ArgLocation: &ArgLoc))
454 return;
455
456 D->addAttr(A: ::new (Context) WebAssemblyExportNameAttr(Context, AL, Str));
457 D->addAttr(A: UsedAttr::CreateImplicit(Ctx&: Context));
458}
459
460} // namespace clang
461