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