1 //===--- LRTable.h - Define LR Parsing Table ---------------------*- C++-*-===// 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 // The LRTable (referred as LR parsing table in the LR literature) is the core 10 // component in LR parsers, it drives the LR parsers by specifying an action to 11 // take given the current state on the top of the stack and the current 12 // lookahead token. 13 // 14 // The LRTable can be described as a matrix where the rows represent 15 // the states of the LR graph, the columns represent the symbols of the 16 // grammar, and each entry of the matrix (called action) represents a 17 // state transition in the graph. 18 // 19 // Typically, based on the category of the grammar symbol, the LRTable is 20 // broken into two logically separate tables: 21 // - ACTION table with terminals as columns -- e.g. ACTION[S, a] specifies 22 // next action (shift/reduce) on state S under a lookahead terminal a 23 // - GOTO table with nonterminals as columns -- e.g. GOTO[S, X] specifies 24 // the state which we transist to from the state S with the nonterminal X 25 // 26 // LRTable is *performance-critial* as it is consulted frequently during a 27 // parse. In general, LRTable is very sparse (most of the entries are empty). 28 // For example, for the C++ language, the SLR table has ~1500 states and 650 29 // symbols which results in a matrix having 975K entries, ~90% of entries are 30 // empty. 31 // 32 // This file implements a speed-and-space-efficient LRTable. 33 // 34 //===----------------------------------------------------------------------===// 35 36 #ifndef CLANG_PSEUDO_GRAMMAR_LRTABLE_H 37 #define CLANG_PSEUDO_GRAMMAR_LRTABLE_H 38 39 #include "clang-pseudo/grammar/Grammar.h" 40 #include "llvm/ADT/ArrayRef.h" 41 #include "llvm/ADT/BitVector.h" 42 #include "llvm/ADT/SmallSet.h" 43 #include "llvm/Support/Capacity.h" 44 #include "llvm/Support/MathExtras.h" 45 #include <cstdint> 46 #include <vector> 47 48 namespace clang { 49 namespace pseudo { 50 51 // Represents the LR parsing table, which can efficiently the question "what is 52 // the next step given the lookahead token and current state on top of the 53 // stack?". 54 // 55 // This is a dense implementation, which only takes an amount of space that is 56 // proportional to the number of non-empty entries in the table. 57 // 58 // Unlike the typical LR parsing table which allows at most one available action 59 // per entry, conflicted actions are allowed in LRTable. The LRTable is designed 60 // to be used in nondeterministic LR parsers (e.g. GLR). 61 // 62 // There are no "accept" actions in the LRTable, instead the stack is inspected 63 // after parsing completes: is the state goto(StartState, StartSymbol)? 64 class LRTable { 65 public: 66 // StateID is only 13 bits wide. 67 using StateID = uint16_t; 68 static constexpr unsigned StateBits = 13; 69 70 struct Recovery { 71 ExtensionID Strategy; 72 SymbolID Result; 73 }; 74 75 // Returns the state after we reduce a nonterminal. 76 // Expected to be called by LR parsers. 77 // If the nonterminal is invalid here, returns None. getGoToState(StateID State,SymbolID Nonterminal)78 llvm::Optional<StateID> getGoToState(StateID State, 79 SymbolID Nonterminal) const { 80 return Gotos.get(gotoIndex(State, Nonterminal, numStates())); 81 } 82 // Returns the state after we shift a terminal. 83 // Expected to be called by LR parsers. 84 // If the terminal is invalid here, returns None. getShiftState(StateID State,SymbolID Terminal)85 llvm::Optional<StateID> getShiftState(StateID State, 86 SymbolID Terminal) const { 87 return Shifts.get(shiftIndex(State, Terminal, numStates())); 88 } 89 90 // Returns the possible reductions from a state. 91 // 92 // These are not keyed by a lookahead token. Instead, call canFollow() to 93 // check whether a reduction should apply in the current context: 94 // for (RuleID R : LR.getReduceRules(S)) { 95 // if (!LR.canFollow(G.lookupRule(R).Target, NextToken)) 96 // continue; 97 // // ...apply reduce... 98 // } getReduceRules(StateID State)99 llvm::ArrayRef<RuleID> getReduceRules(StateID State) const { 100 assert(State + 1u < ReduceOffset.size()); 101 return llvm::makeArrayRef(Reduces.data() + ReduceOffset[State], 102 Reduces.data() + ReduceOffset[State+1]); 103 } 104 // Returns whether Terminal can follow Nonterminal in a valid source file. canFollow(SymbolID Nonterminal,SymbolID Terminal)105 bool canFollow(SymbolID Nonterminal, SymbolID Terminal) const { 106 assert(isToken(Terminal)); 107 assert(isNonterminal(Nonterminal)); 108 return FollowSets.test(tok::NUM_TOKENS * Nonterminal + 109 symbolToToken(Terminal)); 110 } 111 112 // Looks up available recovery actions if we stopped parsing in this state. getRecovery(StateID State)113 llvm::ArrayRef<Recovery> getRecovery(StateID State) const { 114 return llvm::makeArrayRef(Recoveries.data() + RecoveryOffset[State], 115 Recoveries.data() + RecoveryOffset[State + 1]); 116 } 117 118 // Returns the state from which the LR parser should start to parse the input 119 // tokens as the given StartSymbol. 120 // 121 // In LR parsing, the start state of `translation-unit` corresponds to 122 // `_ := • translation-unit`. 123 // 124 // Each start state responds to **a** single grammar rule like `_ := start`. 125 // REQUIRE: The given StartSymbol must exist in the grammar (in a form of 126 // `_ := start`). 127 StateID getStartState(SymbolID StartSymbol) const; 128 bytes()129 size_t bytes() const { 130 return sizeof(*this) + Gotos.bytes() + Shifts.bytes() + 131 llvm::capacity_in_bytes(Reduces) + 132 llvm::capacity_in_bytes(ReduceOffset) + 133 llvm::capacity_in_bytes(FollowSets); 134 } 135 136 std::string dumpStatistics() const; 137 std::string dumpForTests(const Grammar &G) const; 138 139 // Build a SLR(1) parsing table. 140 static LRTable buildSLR(const Grammar &G); 141 142 // Helper for building a table with specified actions/states. 143 struct Builder { 144 Builder() = default; BuilderBuilder145 Builder(const Grammar &G) { 146 NumNonterminals = G.table().Nonterminals.size(); 147 FollowSets = followSets(G); 148 } 149 150 unsigned int NumNonterminals = 0; 151 // States representing `_ := . start` for various start symbols. 152 std::vector<std::pair<SymbolID, StateID>> StartStates; 153 // State transitions `X := ABC . D EFG` => `X := ABC D . EFG`. 154 // Key is (initial state, D), value is final state. 155 llvm::DenseMap<std::pair<StateID, SymbolID>, StateID> Transition; 156 // Reductions available in a given state. 157 llvm::DenseMap<StateID, llvm::SmallSet<RuleID, 4>> Reduce; 158 // FollowSets[NT] is the set of terminals that can follow the nonterminal. 159 std::vector<llvm::DenseSet<SymbolID>> FollowSets; 160 // Recovery options available at each state. 161 std::vector<std::pair<StateID, Recovery>> Recoveries; 162 163 LRTable build() &&; 164 }; 165 166 private: numStates()167 unsigned numStates() const { return ReduceOffset.size() - 1; } 168 169 // A map from unsigned key => StateID, used to store actions. 170 // The keys should be sequential but the values are somewhat sparse. 171 // 172 // In practice, the keys encode (origin state, symbol) pairs, and the values 173 // are the state we should move to after seeing that symbol. 174 // 175 // We store one bit for presence/absence of the value for each key. 176 // At every 64th key, we store the offset into the table of values. 177 // e.g. key 0x500 is checkpoint 0x500/64 = 20 178 // Checkpoints[20] = 34 179 // get(0x500) = Values[34] (assuming it has a value) 180 // To look up values in between, we count the set bits: 181 // get(0x509) has a value if HasValue[20] & (1<<9) 182 // #values between 0x500 and 0x509: popcnt(HasValue[20] & (1<<9 - 1)) 183 // get(0x509) = Values[34 + popcnt(...)] 184 // 185 // Overall size is 1.25 bits/key + 16 bits/value. 186 // Lookup is constant time with a low factor (no hashing). 187 class TransitionTable { 188 using Word = uint64_t; 189 constexpr static unsigned WordBits = CHAR_BIT * sizeof(Word); 190 191 std::vector<StateID> Values; 192 std::vector<Word> HasValue; 193 std::vector<uint16_t> Checkpoints; 194 195 public: 196 TransitionTable() = default; TransitionTable(const llvm::DenseMap<unsigned,StateID> & Entries,unsigned NumKeys)197 TransitionTable(const llvm::DenseMap<unsigned, StateID> &Entries, 198 unsigned NumKeys) { 199 assert( 200 Entries.size() < 201 std::numeric_limits<decltype(Checkpoints)::value_type>::max() && 202 "16 bits too small for value offsets!"); 203 unsigned NumWords = (NumKeys + WordBits - 1) / WordBits; 204 HasValue.resize(NumWords, 0); 205 Checkpoints.reserve(NumWords); 206 Values.reserve(Entries.size()); 207 for (unsigned I = 0; I < NumKeys; ++I) { 208 if ((I % WordBits) == 0) 209 Checkpoints.push_back(Values.size()); 210 auto It = Entries.find(I); 211 if (It != Entries.end()) { 212 HasValue[I / WordBits] |= (Word(1) << (I % WordBits)); 213 Values.push_back(It->second); 214 } 215 } 216 } 217 get(unsigned Key)218 llvm::Optional<StateID> get(unsigned Key) const { 219 // Do we have a value for this key? 220 Word KeyMask = Word(1) << (Key % WordBits); 221 unsigned KeyWord = Key / WordBits; 222 if ((HasValue[KeyWord] & KeyMask) == 0) 223 return llvm::None; 224 // Count the number of values since the checkpoint. 225 Word BelowKeyMask = KeyMask - 1; 226 unsigned CountSinceCheckpoint = 227 llvm::countPopulation(HasValue[KeyWord] & BelowKeyMask); 228 // Find the value relative to the last checkpoint. 229 return Values[Checkpoints[KeyWord] + CountSinceCheckpoint]; 230 } 231 size()232 unsigned size() const { return Values.size(); } 233 bytes()234 size_t bytes() const { 235 return llvm::capacity_in_bytes(HasValue) + 236 llvm::capacity_in_bytes(Values) + 237 llvm::capacity_in_bytes(Checkpoints); 238 } 239 }; 240 // Shift and Goto tables are keyed by encoded (State, Symbol). shiftIndex(StateID State,SymbolID Terminal,unsigned NumStates)241 static unsigned shiftIndex(StateID State, SymbolID Terminal, 242 unsigned NumStates) { 243 return NumStates * symbolToToken(Terminal) + State; 244 } gotoIndex(StateID State,SymbolID Nonterminal,unsigned NumStates)245 static unsigned gotoIndex(StateID State, SymbolID Nonterminal, 246 unsigned NumStates) { 247 assert(isNonterminal(Nonterminal)); 248 return NumStates * Nonterminal + State; 249 } 250 TransitionTable Shifts; 251 TransitionTable Gotos; 252 253 // A sorted table, storing the start state for each target parsing symbol. 254 std::vector<std::pair<SymbolID, StateID>> StartStates; 255 256 // Given a state ID S, the half-open range of Reduces is 257 // [ReduceOffset[S], ReduceOffset[S+1]) 258 std::vector<uint32_t> ReduceOffset; 259 std::vector<RuleID> Reduces; 260 // Conceptually this is a bool[SymbolID][Token], each entry describing whether 261 // the grammar allows the (nonterminal) symbol to be followed by the token. 262 // 263 // This is flattened by encoding the (SymbolID Nonterminal, tok::Kind Token) 264 // as an index: Nonterminal * NUM_TOKENS + Token. 265 llvm::BitVector FollowSets; 266 267 // Recovery stores all recovery actions from all states. 268 // A given state has [RecoveryOffset[S], RecoveryOffset[S+1]). 269 std::vector<uint32_t> RecoveryOffset; 270 std::vector<Recovery> Recoveries; 271 }; 272 273 } // namespace pseudo 274 } // namespace clang 275 276 #endif // CLANG_PSEUDO_GRAMMAR_LRTABLE_H 277