490 lines · cpp
1//===- WasmAsmParser.cpp - Wasm Assembly Parser -----------------------------===//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-exception6//7// --8//9// Note, this is for wasm, the binary format (analogous to ELF), not wasm,10// the instruction set (analogous to x86), for which parsing code lives in11// WebAssemblyAsmParser.12//13// This file contains processing for generic directives implemented using14// MCTargetStreamer, the ones that depend on WebAssemblyTargetStreamer are in15// WebAssemblyAsmParser.16//17//===----------------------------------------------------------------------===//18 19#include "llvm/ADT/StringExtras.h"20#include "llvm/BinaryFormat/Wasm.h"21#include "llvm/MC/MCContext.h"22#include "llvm/MC/MCExpr.h"23#include "llvm/MC/MCObjectFileInfo.h"24#include "llvm/MC/MCParser/AsmLexer.h"25#include "llvm/MC/MCParser/MCAsmParser.h"26#include "llvm/MC/MCParser/MCAsmParserExtension.h"27#include "llvm/MC/MCSectionWasm.h"28#include "llvm/MC/MCStreamer.h"29#include "llvm/MC/MCSymbol.h"30#include "llvm/MC/MCSymbolWasm.h"31#include <optional>32 33using namespace llvm;34 35namespace {36 37class WasmAsmParser : public MCAsmParserExtension {38 MCAsmParser *Parser = nullptr;39 AsmLexer *Lexer = nullptr;40 41 template<bool (WasmAsmParser::*HandlerMethod)(StringRef, SMLoc)>42 void addDirectiveHandler(StringRef Directive) {43 MCAsmParser::ExtensionDirectiveHandler Handler = std::make_pair(44 this, HandleDirective<WasmAsmParser, HandlerMethod>);45 46 getParser().addDirectiveHandler(Directive, Handler);47 }48 49public:50 WasmAsmParser() { BracketExpressionsSupported = true; }51 52 void Initialize(MCAsmParser &P) override {53 Parser = &P;54 Lexer = &Parser->getLexer();55 // Call the base implementation.56 this->MCAsmParserExtension::Initialize(*Parser);57 58 addDirectiveHandler<&WasmAsmParser::parseSectionDirectiveText>(".text");59 addDirectiveHandler<&WasmAsmParser::parseSectionDirectiveData>(".data");60 addDirectiveHandler<&WasmAsmParser::parseSectionDirective>(".section");61 addDirectiveHandler<&WasmAsmParser::parseDirectivePrevious>(".previous");62 addDirectiveHandler<&WasmAsmParser::parseDirectiveSize>(".size");63 addDirectiveHandler<&WasmAsmParser::parseDirectiveType>(".type");64 addDirectiveHandler<&WasmAsmParser::ParseDirectiveIdent>(".ident");65 addDirectiveHandler<66 &WasmAsmParser::ParseDirectiveSymbolAttribute>(".weak");67 addDirectiveHandler<68 &WasmAsmParser::ParseDirectiveSymbolAttribute>(".local");69 addDirectiveHandler<70 &WasmAsmParser::ParseDirectiveSymbolAttribute>(".internal");71 addDirectiveHandler<72 &WasmAsmParser::ParseDirectiveSymbolAttribute>(".hidden");73 addDirectiveHandler<&WasmAsmParser::parseDirectiveSymver>(".symver");74 }75 76 bool error(const StringRef &Msg, const AsmToken &Tok) {77 return Parser->Error(Tok.getLoc(), Msg + Tok.getString());78 }79 80 bool isNext(AsmToken::TokenKind Kind) {81 auto Ok = Lexer->is(Kind);82 if (Ok)83 Lex();84 return Ok;85 }86 87 bool expect(AsmToken::TokenKind Kind, const char *KindName) {88 if (!isNext(Kind))89 return error(std::string("Expected ") + KindName + ", instead got: ",90 Lexer->getTok());91 return false;92 }93 94 bool parseSectionDirectiveText(StringRef, SMLoc) {95 // FIXME: .text currently no-op.96 return false;97 }98 99 bool parseSectionDirectiveData(StringRef, SMLoc) {100 auto *S = getContext().getObjectFileInfo()->getDataSection();101 getStreamer().switchSection(S);102 return false;103 }104 105 uint32_t parseSectionFlags(StringRef FlagStr, bool &Passive, bool &Group) {106 uint32_t flags = 0;107 for (char C : FlagStr) {108 switch (C) {109 case 'p':110 Passive = true;111 break;112 case 'G':113 Group = true;114 break;115 case 'T':116 flags |= wasm::WASM_SEG_FLAG_TLS;117 break;118 case 'S':119 flags |= wasm::WASM_SEG_FLAG_STRINGS;120 break;121 case 'R':122 flags |= wasm::WASM_SEG_FLAG_RETAIN;123 break;124 default:125 return -1U;126 }127 }128 return flags;129 }130 131 bool parseGroup(StringRef &GroupName) {132 if (Lexer->isNot(AsmToken::Comma))133 return TokError("expected group name");134 Lex();135 if (Lexer->is(AsmToken::Integer)) {136 GroupName = getTok().getString();137 Lex();138 } else if (Parser->parseIdentifier(GroupName)) {139 return TokError("invalid group name");140 }141 if (Lexer->is(AsmToken::Comma)) {142 Lex();143 StringRef Linkage;144 if (Parser->parseIdentifier(Linkage))145 return TokError("invalid linkage");146 if (Linkage != "comdat")147 return TokError("Linkage must be 'comdat'");148 }149 return false;150 }151 152 // A section name can contain '-' (e.g. glibc's .gnu.glibc-stub.* markers) and153 // other characters that are not part of a single lexer identifier token, so154 // -- exactly like ELFAsmParser::parseSectionName -- we cannot just use155 // parseIdentifier. Build the name from adjacent tokens up to the comma / end156 // of statement.157 bool parseSectionName(StringRef &SectionName) {158 SMLoc FirstLoc = Lexer->getLoc();159 unsigned Size = 0;160 161 if (Lexer->is(AsmToken::String)) {162 SectionName = getTok().getIdentifier();163 Lex();164 return false;165 }166 167 while (!Parser->hasPendingError()) {168 SMLoc PrevLoc = Lexer->getLoc();169 if (Lexer->is(AsmToken::Comma) || Lexer->is(AsmToken::EndOfStatement))170 break;171 172 unsigned CurSize;173 if (Lexer->is(AsmToken::String)) {174 CurSize = getTok().getIdentifier().size() + 2;175 Lex();176 } else if (Lexer->is(AsmToken::Identifier)) {177 CurSize = getTok().getIdentifier().size();178 Lex();179 } else {180 CurSize = getTok().getString().size();181 Lex();182 }183 Size += CurSize;184 SectionName = StringRef(FirstLoc.getPointer(), Size);185 186 // Make sure the following token is adjacent.187 if (PrevLoc.getPointer() + CurSize != getTok().getLoc().getPointer())188 break;189 }190 if (Size == 0)191 return true;192 193 return false;194 }195 196 bool parseSectionDirective(StringRef, SMLoc loc) {197 StringRef Name;198 if (parseSectionName(Name))199 return TokError("expected identifier in directive");200 201 auto Kind = StringSwitch<std::optional<SectionKind>>(Name)202 .StartsWith(".data", SectionKind::getData())203 .StartsWith(".tdata", SectionKind::getThreadData())204 .StartsWith(".tbss", SectionKind::getThreadBSS())205 .StartsWith(".rodata", SectionKind::getReadOnly())206 .StartsWith(".text", SectionKind::getText())207 .StartsWith(".custom_section", SectionKind::getMetadata())208 .StartsWith(".bss", SectionKind::getBSS())209 // See use of .init_array in WasmObjectWriter and210 // TargetLoweringObjectFileWasm211 .StartsWith(".init_array", SectionKind::getData())212 .StartsWith(".debug_", SectionKind::getMetadata())213 .Default(SectionKind::getData());214 215 // GNU single-operand form: `.section <name>` with no flags/type operand.216 // GNU as accepts this bare spelling; glibc emits it (via the217 // link_warning / stub_warning machinery in include/libc-symbols.h) for218 // unallocated diagnostic marker sections such as `.gnu.warning.<sym>` and219 // `.gnu.glibc-stub.<sym>`, each immediately followed by `.previous`. wasm220 // (the object format) and wasm-ld have no ELF-style .gnu.warning /221 // .gnu.glibc-stub linker-warning feature, so these markers are purely222 // diagnostic and carry no allocated content. Accept the form and switch to223 // the (empty, unallocated) section so that a following `.previous` returns224 // to the prior section via the normal section stack; the marker itself225 // contributes nothing to the output. This never errors on a representable226 // construct and never drops real content: anything subsequently emitted227 // here still has a home in this section, exactly as on ELF.228 if (Lexer->is(AsmToken::EndOfStatement)) {229 MCSectionWasm *WS = getContext().getWasmSection(Name, *Kind);230 getStreamer().switchSection(WS);231 return false;232 }233 234 if (expect(AsmToken::Comma, ","))235 return true;236 237 if (Lexer->isNot(AsmToken::String))238 return error("expected string in directive, instead got: ", Lexer->getTok());239 240 // Update section flags if present in this .section directive241 bool Passive = false;242 bool Group = false;243 uint32_t Flags =244 parseSectionFlags(getTok().getStringContents(), Passive, Group);245 if (Flags == -1U)246 return TokError("unknown flag");247 248 Lex();249 250 if (expect(AsmToken::Comma, ",") || expect(AsmToken::At, "@"))251 return true;252 253 StringRef GroupName;254 if (Group && parseGroup(GroupName))255 return true;256 257 if (expect(AsmToken::EndOfStatement, "eol"))258 return true;259 260 // TODO: Parse UniqueID261 MCSectionWasm *WS = getContext().getWasmSection(262 Name, *Kind, Flags, GroupName, MCSection::NonUniqueID);263 264 if (WS->getSegmentFlags() != Flags)265 Parser->Error(loc, "changed section flags for " + Name +266 ", expected: 0x" +267 utohexstr(WS->getSegmentFlags()));268 269 if (Passive) {270 if (!WS->isWasmData())271 return Parser->Error(loc, "Only data sections can be passive");272 WS->setPassive();273 }274 275 getStreamer().switchSection(WS);276 return false;277 }278 279 /// parseDirectivePrevious280 /// ::= .previous281 ///282 /// Switch back to the section that was active before the most recent283 /// `.section` / section switch, using the streamer's section stack -- the284 /// same semantics as ELFAsmParser::parseDirectivePrevious. glibc pairs every285 /// `.section .gnu.warning.*` / `.section .gnu.glibc-stub.*` marker with a286 /// `.previous` to restore the prior section; this was previously rejected on287 /// wasm as an "unknown directive". Mirror ELF exactly so the round-trip288 /// (`.section A` ... `.previous` -> back in A's predecessor) is preserved.289 bool parseDirectivePrevious(StringRef, SMLoc) {290 MCSectionSubPair PreviousSection = getStreamer().getPreviousSection();291 if (PreviousSection.first == nullptr)292 return TokError(".previous without corresponding .section");293 getStreamer().switchSection(PreviousSection.first, PreviousSection.second);294 return false;295 }296 297 // TODO: This function is almost the same as ELFAsmParser::ParseDirectiveSize298 // so maybe could be shared somehow.299 bool parseDirectiveSize(StringRef, SMLoc Loc) {300 MCSymbol *Sym;301 if (Parser->parseSymbol(Sym))302 return TokError("expected identifier in directive");303 if (expect(AsmToken::Comma, ","))304 return true;305 const MCExpr *Expr;306 if (Parser->parseExpression(Expr))307 return true;308 if (expect(AsmToken::EndOfStatement, "eol"))309 return true;310 auto WasmSym = static_cast<const MCSymbolWasm *>(Sym);311 if (WasmSym->isFunction()) {312 // Ignore .size directives for function symbols. They get their size313 // set automatically based on their content.314 Warning(Loc, ".size directive ignored for function symbols");315 } else {316 getStreamer().emitELFSize(Sym, Expr);317 }318 return false;319 }320 321 bool parseDirectiveType(StringRef, SMLoc) {322 // This could be the start of a function, check if followed by323 // "label,@function"324 if (!Lexer->is(AsmToken::Identifier))325 return error("Expected label after .type directive, got: ",326 Lexer->getTok());327 auto *WasmSym = static_cast<MCSymbolWasm *>(328 getStreamer().getContext().parseSymbol(Lexer->getTok().getString()));329 Lex();330 // GNU `as` accepts both spellings of the symbol-type prefix: `@type` and331 // `%type` (e.g. `%object`, `%function`, `%gnu_indirect_function`). Stock332 // glibc's include/libc-symbols.h hardcodes the `%` form (e.g.333 // declare_object_symbol_alias_1 emits `.type sym, %object`), which the wasm334 // MC parser previously rejected, accepting only `@`. Accept `%` as an exact335 // synonym for `@` here -- mirroring ELFAsmParser, which accepts the `@`,336 // `#`, and `%` prefixes interchangeably. The prefix only selects the337 // spelling; the type name that follows is interpreted by the identical338 // switch below, so `%X` produces exactly the same wasm symbol type as `@X`339 // (and remains rejected for any name the `@` form would also reject). This340 // never errors on a representable construct and does not change `@`-form341 // behavior.342 if (!(isNext(AsmToken::Comma) &&343 (isNext(AsmToken::At) || isNext(AsmToken::Percent)) &&344 Lexer->is(AsmToken::Identifier)))345 return error("Expected label,@type declaration, got: ", Lexer->getTok());346 auto TypeName = Lexer->getTok().getString();347 if (TypeName == "function") {348 WasmSym->setType(wasm::WASM_SYMBOL_TYPE_FUNCTION);349 auto *Current =350 static_cast<MCSectionWasm *>(getStreamer().getCurrentSectionOnly());351 if (Current->getGroup())352 WasmSym->setComdat(true);353 } else if (TypeName == "global")354 WasmSym->setType(wasm::WASM_SYMBOL_TYPE_GLOBAL);355 else if (TypeName == "object")356 WasmSym->setType(wasm::WASM_SYMBOL_TYPE_DATA);357 else358 return error("Unknown WASM symbol type: ", Lexer->getTok());359 Lex();360 return expect(AsmToken::EndOfStatement, "EOL");361 }362 363 // FIXME: Shared with ELF.364 /// ParseDirectiveIdent365 /// ::= .ident string366 bool ParseDirectiveIdent(StringRef, SMLoc) {367 if (getLexer().isNot(AsmToken::String))368 return TokError("unexpected token in '.ident' directive");369 StringRef Data = getTok().getIdentifier();370 Lex();371 if (getLexer().isNot(AsmToken::EndOfStatement))372 return TokError("unexpected token in '.ident' directive");373 Lex();374 getStreamer().emitIdent(Data);375 return false;376 }377 378 // FIXME: Shared with ELF.379 /// ParseDirectiveSymbolAttribute380 /// ::= { ".local", ".weak", ... } [ identifier ( , identifier )* ]381 bool ParseDirectiveSymbolAttribute(StringRef Directive, SMLoc) {382 MCSymbolAttr Attr = StringSwitch<MCSymbolAttr>(Directive)383 .Case(".weak", MCSA_Weak)384 .Case(".local", MCSA_Local)385 .Case(".hidden", MCSA_Hidden)386 .Case(".internal", MCSA_Internal)387 .Case(".protected", MCSA_Protected)388 .Default(MCSA_Invalid);389 assert(Attr != MCSA_Invalid && "unexpected symbol attribute directive!");390 if (getLexer().isNot(AsmToken::EndOfStatement)) {391 while (true) {392 MCSymbol *Sym;393 if (getParser().parseSymbol(Sym))394 return TokError("expected identifier in directive");395 getStreamer().emitSymbolAttribute(Sym, Attr);396 if (getLexer().is(AsmToken::EndOfStatement))397 break;398 if (getLexer().isNot(AsmToken::Comma))399 return TokError("unexpected token in directive");400 Lex();401 }402 }403 Lex();404 return false;405 }406 407 /// parseDirectiveSymver408 /// ::= .symver foo, bar@@@ver409 ///410 /// wasm (the object format) has no ELF-style symbol versioning, and this is a411 /// fresh ABI with no legacy binaries, so version selection collapses to "the412 /// default version only". We map the directive onto the existing assembler413 /// alias machinery (the same representation extended by the prior414 /// alias-to-undefined-target fix in WasmObjectWriter):415 ///416 /// - default version (`bar@@ver`): make `bar` a public alias of `foo`,417 /// dropping the version tag. `bar` then resolves to `foo` at link, exactly418 /// like `.set bar, foo` followed by `.globl bar`.419 /// - non-default / compat version (`bar@ver`, single `@`): a compat symbol420 /// for OLD binaries, which do not exist on this fresh ABI. Emitting a421 /// second public `bar` here would collide with the default `bar`, so the422 /// compat alias is dropped. `foo` itself is left untouched (it remains a423 /// defined symbol in its own right).424 ///425 /// This never errors with "unknown directive" and never mis-resolves: the426 /// default binding is always materialized correctly, and we never emit two427 /// conflicting public definitions of the base name.428 bool parseDirectiveSymver(StringRef, SMLoc Loc) {429 MCSymbol *OriginalSym;430 if (getParser().parseSymbol(OriginalSym))431 return TokError("expected identifier in .symver directive");432 433 if (getLexer().isNot(AsmToken::Comma))434 return TokError("expected a comma in .symver directive");435 436 // '@' is a comment in some assembly dialects, but is required inside the437 // versioned name here. Allow it for the next identifier only, mirroring438 // ELFAsmParser::parseDirectiveSymver.439 const bool AllowAtInIdentifier = getLexer().getAllowAtInIdentifier();440 getLexer().setAllowAtInIdentifier(true);441 Lex();442 getLexer().setAllowAtInIdentifier(AllowAtInIdentifier);443 444 StringRef Name;445 if (getParser().parseIdentifier(Name))446 return TokError("expected identifier in .symver directive");447 448 size_t Pos = Name.find('@');449 if (Pos == StringRef::npos)450 return Error(Loc, "expected a '@' in the .symver versioned name");451 452 // Accept (and ignore) the optional ", remove" tail and the end of line, so453 // every well-formed ELF `.symver` spelling is parsed rather than rejected.454 if (parseOptionalToken(AsmToken::Comma)) {455 StringRef Action;456 if (getParser().parseIdentifier(Action) || Action != "remove")457 return TokError("expected 'remove'");458 }459 (void)parseOptionalToken(AsmToken::EndOfStatement);460 461 StringRef BareName = Name.substr(0, Pos);462 StringRef Rest = Name.substr(Pos); // one or more leading '@'463 // "@@" (and "@@@") select the default version; a single "@" is a464 // non-default / compat version.465 bool IsDefault = Rest.starts_with("@@");466 467 if (!IsDefault) {468 // Compat version: drop it. No legacy binaries exist on this ABI, so469 // nothing consumes the compat alias, and creating a second public470 // `BareName` would collide with the default. Never-silent: we deliberately471 // omit a consumer-less compat alias rather than mis-resolve or duplicate.472 return false;473 }474 475 // Default version: BareName becomes a public alias of OriginalSym with the476 // version tag dropped. Reuse the assembler-alias representation so the477 // WasmObjectWriter resolves BareName to its base symbol at link time.478 MCSymbol *AliasSym = getContext().getOrCreateSymbol(BareName);479 const MCExpr *Value = MCSymbolRefExpr::create(OriginalSym, getContext());480 getStreamer().emitAssignment(AliasSym, Value);481 // The default version is the public/exported binding of the base name.482 getStreamer().emitSymbolAttribute(AliasSym, MCSA_Global);483 return false;484 }485};486 487} // end anonymous namespace488 489MCAsmParserExtension *llvm::createWasmAsmParser() { return new WasmAsmParser; }490