22#include " chunk.hpp"
33#include " value.hpp"
44#include < algorithm>
5+ #include < memory>
56#include < stdexcept>
67#include < string>
78#ifdef LOX_DEBUG
1112namespace lox {
1213namespace optimise {
1314
15+ bool AddLocalConstOptimisation::matches (const Chunk& chunk, const ChunkInfo& ci,
16+ size_t instruction_index) const {
17+ if (instruction_index + match_length () - 1 >= ci.instruction_offsets .size ()) {
18+ return false ;
19+ }
20+ size_t i = instruction_index;
21+ if (static_cast <OpCode>(chunk.at (ci.instruction_offsets [i])) ==
22+ OpCode::GET_LOCAL &&
23+ static_cast <OpCode>(chunk.at (ci.instruction_offsets [i + 1 ])) ==
24+ OpCode::CONSTANT &&
25+ static_cast <OpCode>(chunk.at (ci.instruction_offsets [i + 2 ])) ==
26+ OpCode::ADD &&
27+ static_cast <OpCode>(chunk.at (ci.instruction_offsets [i + 3 ])) ==
28+ OpCode::SET_LOCAL &&
29+ static_cast <OpCode>(chunk.at (ci.instruction_offsets [i + 4 ])) ==
30+ OpCode::POP ) {
31+ uint8_t get_local_index = chunk.at (ci.instruction_offsets [i] + 1 );
32+ uint8_t set_local_index = chunk.at (ci.instruction_offsets [i + 3 ] + 1 );
33+ if (get_local_index == set_local_index &&
34+ /* make sure that nothing tries to jump to the middle of this sequence
35+ */
36+ !(ci.jump_targets .contains (ci.instruction_offsets [i + 1 ]) ||
37+ ci.jump_targets .contains (ci.instruction_offsets [i + 2 ]) ||
38+ ci.jump_targets .contains (ci.instruction_offsets [i + 3 ]) ||
39+ ci.jump_targets .contains (ci.instruction_offsets [i + 4 ]))) {
40+ return true ;
41+ }
42+ }
43+ return false ;
44+ }
45+
46+ size_t AddLocalConstOptimisation::match_length () const { return 5 ; }
47+
48+ std::pair<size_t , size_t >
49+ AddLocalConstOptimisation::emit (const Chunk& old_chunk, size_t old_byte_offset,
50+ Chunk& new_chunk) const {
51+ size_t line_number = old_chunk.debuginfo_at (old_byte_offset);
52+ size_t local_index = old_chunk.at (old_byte_offset + 1 );
53+ size_t constant_index = old_chunk.at (old_byte_offset + 3 );
54+ new_chunk.write (static_cast <uint8_t >(OpCode::ADD_LOCAL_CONST ), line_number);
55+ new_chunk.write (local_index, line_number);
56+ new_chunk.write (constant_index, line_number);
57+ return {8 , 3 }; // 8 bytes read from old chunk, 3 bytes written to new chunk
58+ }
59+
60+ bool LocalConstLessOptimisation::matches (const Chunk& chunk,
61+ const ChunkInfo& ci,
62+ size_t instruction_index) const {
63+ if (instruction_index + match_length () - 1 >= ci.instruction_offsets .size ()) {
64+ return false ;
65+ }
66+ size_t i = instruction_index;
67+ if (static_cast <OpCode>(chunk.at (ci.instruction_offsets [i])) ==
68+ OpCode::GET_LOCAL &&
69+ static_cast <OpCode>(chunk.at (ci.instruction_offsets [i + 1 ])) ==
70+ OpCode::CONSTANT &&
71+ static_cast <OpCode>(chunk.at (ci.instruction_offsets [i + 2 ])) ==
72+ OpCode::LESS ) {
73+ if (!(ci.jump_targets .contains (ci.instruction_offsets [i + 1 ]) ||
74+ ci.jump_targets .contains (ci.instruction_offsets [i + 2 ]))) {
75+ return true ;
76+ }
77+ }
78+ return false ;
79+ }
80+
81+ size_t LocalConstLessOptimisation::match_length () const { return 3 ; }
82+
83+ std::pair<size_t , size_t >
84+ LocalConstLessOptimisation::emit (const Chunk& old_chunk, size_t old_byte_offset,
85+ Chunk& new_chunk) const {
86+ size_t line_number = old_chunk.debuginfo_at (old_byte_offset);
87+ size_t local_index = old_chunk.at (old_byte_offset + 1 );
88+ size_t constant_index = old_chunk.at (old_byte_offset + 3 );
89+ new_chunk.write (static_cast <uint8_t >(OpCode::LOCAL_CONST_LESS ), line_number);
90+ new_chunk.write (local_index, line_number);
91+ new_chunk.write (constant_index, line_number);
92+ return {5 , 3 };
93+ }
94+
1495ChunkInfo::ChunkInfo (const Chunk& chunk) : jumps(), instruction_offsets() {
1596 size_t offset = 0 ;
1697 while (offset < chunk.size ()) {
@@ -20,8 +101,8 @@ ChunkInfo::ChunkInfo(const Chunk& chunk) : jumps(), instruction_offsets() {
20101 uint8_t high_byte = chunk.at (offset + 1 );
21102 uint8_t low_byte = chunk.at (offset + 2 );
22103 ptrdiff_t jump_offset = lox::get_jump_offset (high_byte, low_byte);
23- // check for negative targets (just in case). note that we have to add 3
24- // to the offset here, because we jump after reading the offset
104+ // check for negative targets (just in case). note that we have to add
105+ // 3 to the offset here, because we jump after reading the offset
25106 ptrdiff_t tmp = static_cast <ptrdiff_t >(offset) + 3 + jump_offset;
26107 if (tmp < 0 ) {
27108 std::cerr << offset << " " << jump_offset << " \n " ;
@@ -56,8 +137,8 @@ ChunkInfo::ChunkInfo(const Chunk& chunk) : jumps(), instruction_offsets() {
56137}
57138
58139/* Return the offset of the next instruction when linearly scanning through
59- * the bytecode. If the current instruction is the last in the chunk, returns
60- * the offset of the end of the chunk. */
140+ * the bytecode. If the current instruction is the last in the chunk,
141+ * returns the offset of the end of the chunk. */
61142size_t next_instruction (const Chunk& chunk, size_t offset) {
62143 size_t nbytes = chunk.size ();
63144 if (offset > nbytes) {
@@ -94,7 +175,6 @@ size_t next_instruction(const Chunk& chunk, size_t offset) {
94175 case OpCode::LESS :
95176 case OpCode::PRINT :
96177 case OpCode::POP :
97- case OpCode::NOP :
98178 case OpCode::INHERIT : {
99179 return offset + 1 ;
100180 }
@@ -116,6 +196,7 @@ size_t next_instruction(const Chunk& chunk, size_t offset) {
116196 }
117197
118198 case OpCode::ADD_LOCAL_CONST :
199+ case OpCode::LOCAL_CONST_LESS :
119200 case OpCode::INVOKE :
120201 case OpCode::SUPER_INVOKE :
121202 case OpCode::JUMP_IF_FALSE :
@@ -127,81 +208,58 @@ size_t next_instruction(const Chunk& chunk, size_t offset) {
127208 std::to_string (instruction));
128209}
129210
130- std::unordered_map<size_t , AddLocalConstInfo>
131- get_alc_opportunities (const Chunk& chunk, const ChunkInfo& ci) {
132- std::unordered_map<size_t , AddLocalConstInfo> add_local_const_offsets;
133- // Gather potential optimisation opportunities for ADD_LOCAL_CONST
134- for (size_t i = 0 ; i + 4 < ci.instruction_offsets .size (); i++) {
135- if (static_cast <OpCode>(chunk.at (ci.instruction_offsets [i])) ==
136- OpCode::GET_LOCAL &&
137- static_cast <OpCode>(chunk.at (ci.instruction_offsets [i + 1 ])) ==
138- OpCode::CONSTANT &&
139- static_cast <OpCode>(chunk.at (ci.instruction_offsets [i + 2 ])) ==
140- OpCode::ADD &&
141- static_cast <OpCode>(chunk.at (ci.instruction_offsets [i + 3 ])) ==
142- OpCode::SET_LOCAL &&
143- static_cast <OpCode>(chunk.at (ci.instruction_offsets [i + 4 ])) ==
144- OpCode::POP ) {
145- uint8_t get_local_index = chunk.at (ci.instruction_offsets [i] + 1 );
146- uint8_t constant_index = chunk.at (ci.instruction_offsets [i + 1 ] + 1 );
147- uint8_t set_local_index = chunk.at (ci.instruction_offsets [i + 3 ] + 1 );
148- if (get_local_index == set_local_index &&
149- /* make sure that nothing tries to jump to the middle of this sequence
150- */
151- !(ci.jump_targets .contains (ci.instruction_offsets [i + 1 ]) ||
152- ci.jump_targets .contains (ci.instruction_offsets [i + 2 ]) ||
153- ci.jump_targets .contains (ci.instruction_offsets [i + 3 ]) ||
154- ci.jump_targets .contains (ci.instruction_offsets [i + 4 ]))) {
155- #ifdef LOX_DEBUG
156- std::cerr << " Found potential ADD_LOCAL_CONST at offset "
157- << ci.instruction_offsets [i] << " with local index "
158- << +get_local_index << " and constant index "
159- << +constant_index << " \n " ;
160- #endif
161- add_local_const_offsets[ci.instruction_offsets [i]] =
162- AddLocalConstInfo{get_local_index, constant_index};
211+ std::unordered_map<size_t , size_t > find_optimisation_offsets (
212+ const Chunk& chunk, const ChunkInfo& ci,
213+ const std::vector<std::unique_ptr<PeepholeOptimisation>>& registry) {
214+
215+ std::unordered_map<size_t , size_t > optimisation_offsets;
216+ size_t i = 0 ;
217+ while (i < ci.instruction_offsets .size ()) {
218+ bool found_opt = false ;
219+ for (size_t opt_num = 0 ; opt_num < registry.size (); opt_num++) {
220+ if (registry[opt_num]->matches (chunk, ci, i)) {
221+ optimisation_offsets[ci.instruction_offsets [i]] = opt_num;
222+ i += registry[opt_num]->match_length ();
223+ found_opt = true ;
224+ break ;
163225 }
164226 }
227+ if (!found_opt) {
228+ i++;
229+ }
165230 }
166- return add_local_const_offsets ;
231+ return optimisation_offsets ;
167232}
168233
169- Chunk apply_alc_opportunities (
234+ Chunk apply_optimisations (
170235 const Chunk& old_chunk, const ChunkInfo& ci,
171- const std::unordered_map<size_t , AddLocalConstInfo>&
172- add_local_const_offsets ) {
236+ const std::unordered_map<size_t , size_t >& optimisation_offsets,
237+ const std::vector<std::unique_ptr<PeepholeOptimisation>>& registry ) {
173238 Chunk new_chunk;
174239
175240 // Rebuild constant table (just the same)
176241 for (size_t i = 0 ; i < old_chunk.constants_size (); i++) {
177242 new_chunk.push_constant (old_chunk.constant_at (i));
178243 }
179244
180- // old offset < => new offset maps
181- // Technically these could be std::vector<size_t>, but well .
245+ // Build a old offset => new offset map so that we can patch jump offsets
246+ // later .
182247 std::unordered_map<size_t , size_t > old_to_new_offset;
183- std::unordered_map<size_t , size_t > new_to_old_offset;
184248
185- // Rebuild bytecode itself
249+ // Rebuild bytecode itself + debuginfo
186250 size_t old_offset = 0 ;
187251 size_t new_offset = 0 ; // Every time we write to chunk we increment this
188252 while (old_offset < old_chunk.size ()) {
189- // Record offset mapping
190253 old_to_new_offset[old_offset] = new_offset;
191- new_to_old_offset[new_offset] = old_offset;
192254 size_t line_number = old_chunk.debuginfo_at (old_offset);
193255
194256 size_t next_old_offset = next_instruction (old_chunk, old_offset);
195- auto it = add_local_const_offsets.find (old_offset);
196- if (it != add_local_const_offsets.end ()) {
197- // replace the next 8 bytes with a single ADD_LOCAL_CONST instruction
198- const AddLocalConstInfo& alc_info = it->second ;
199- new_chunk.write (static_cast <uint8_t >(OpCode::ADD_LOCAL_CONST ),
200- line_number);
201- new_chunk.write (alc_info.local_index , line_number);
202- new_chunk.write (alc_info.constant_index , line_number);
203- old_offset += 8 ;
204- new_offset += 3 ;
257+ auto it = optimisation_offsets.find (old_offset);
258+ if (it != optimisation_offsets.end ()) {
259+ auto [old_bytes_read, new_bytes_written] =
260+ registry[it->second ]->emit (old_chunk, old_offset, new_chunk);
261+ old_offset += old_bytes_read;
262+ new_offset += new_bytes_written;
205263 } else {
206264 // just copy the instruction to the new chunk
207265 for (size_t i = old_offset; i < next_old_offset; i++) {
@@ -222,11 +280,10 @@ Chunk apply_alc_opportunities(
222280 new_target_offset);
223281 // Should not fail but we can check anyway
224282 if (!success) {
225- throw std::runtime_error (
226- " loxc: apply_alc_opportunities: jump offset from " +
227- std::to_string (new_jump_offset) + " to " +
228- std::to_string (new_target_offset) +
229- " is too large to fit in two bytes" );
283+ throw std::runtime_error (" loxc: apply_optimisations: jump offset from " +
284+ std::to_string (new_jump_offset) + " to " +
285+ std::to_string (new_target_offset) +
286+ " is too large to fit in two bytes" );
230287 }
231288 }
232289
@@ -238,8 +295,15 @@ Chunk peephole_optimise(Chunk& chunk) {
238295 return chunk;
239296#else
240297 ChunkInfo chunk_info (chunk);
241- auto add_local_const_offsets = get_alc_opportunities (chunk, chunk_info);
242- return apply_alc_opportunities (chunk, chunk_info, add_local_const_offsets);
298+ // NOTE: Can't use initialiser list because it copies whatever is passed to it
299+ // and unique_ptr can't be copied
300+ std::vector<std::unique_ptr<PeepholeOptimisation>> registry;
301+ registry.push_back (std::make_unique<AddLocalConstOptimisation>());
302+ registry.push_back (std::make_unique<LocalConstLessOptimisation>());
303+
304+ auto optimisation_offsets =
305+ find_optimisation_offsets (chunk, chunk_info, registry);
306+ return apply_optimisations (chunk, chunk_info, optimisation_offsets, registry);
243307#endif
244308}
245309
0 commit comments