Skip to content

Commit d235c25

Browse files
committed
generalise optimisation
1 parent 0d7091a commit d235c25

7 files changed

Lines changed: 186 additions & 77 deletions

File tree

app/main-40b14be5.o.tmp

Whitespace-only changes.

src/chunk.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -226,9 +226,12 @@ size_t lox::Chunk::disassemble(std::ostream& os, size_t offset,
226226
<< " constant_index=" << +constant_index << "\n";
227227
return offset + 3;
228228
}
229-
case OpCode::NOP: {
230-
os << "NOP\n";
231-
return offset + 1;
229+
case OpCode::LOCAL_CONST_LESS: {
230+
uint8_t local_index = code[offset + 1];
231+
uint8_t constant_index = code[offset + 1];
232+
os << "LOCAL_CONST_LESS local_index=" << +local_index
233+
<< " constant_index=" << +constant_index << "\n";
234+
return offset + 3;
232235
}
233236
case OpCode::NEGATE: {
234237
os << "NEGATE\n";

src/opcode_def.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,4 @@
3333
X(GET_SUPER) \
3434
X(SUPER_INVOKE) \
3535
X(ADD_LOCAL_CONST) \
36-
X(NOP)
36+
X(LOCAL_CONST_LESS)

src/optimise.cpp

Lines changed: 129 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include "chunk.hpp"
33
#include "value.hpp"
44
#include <algorithm>
5+
#include <memory>
56
#include <stdexcept>
67
#include <string>
78
#ifdef LOX_DEBUG
@@ -11,6 +12,86 @@
1112
namespace lox {
1213
namespace 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+
1495
ChunkInfo::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. */
61142
size_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

src/optimise.hpp

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,6 @@ namespace lox {
88

99
namespace optimise {
1010

11-
struct AddLocalConstInfo {
12-
uint8_t local_index;
13-
uint8_t constant_index;
14-
};
15-
1611
class ChunkInfo {
1712
public:
1813
/* Map from jump instruction offset to the target instruction offset */
@@ -21,10 +16,39 @@ class ChunkInfo {
2116
std::unordered_set<size_t> jump_targets;
2217
/* Collection of all offsets */
2318
std::vector<size_t> instruction_offsets;
24-
2519
ChunkInfo(const Chunk& chunk);
2620
};
2721

22+
class PeepholeOptimisation {
23+
public:
24+
virtual bool matches(const Chunk& chunk, const ChunkInfo& ci,
25+
size_t offset) const = 0;
26+
virtual size_t match_length() const = 0;
27+
// Returns a pair of (number of bytes in old chunk overwritten, number of
28+
// bytes written to new chunk)
29+
virtual std::pair<size_t, size_t>
30+
emit(const Chunk& old_chunk, size_t old_offset, Chunk& new_chunk) const = 0;
31+
virtual ~PeepholeOptimisation() = default;
32+
};
33+
34+
class AddLocalConstOptimisation : public PeepholeOptimisation {
35+
public:
36+
bool matches(const Chunk& chunk, const ChunkInfo&,
37+
size_t offset) const override;
38+
size_t match_length() const override;
39+
std::pair<size_t, size_t> emit(const Chunk& old_chunk, size_t old_offset,
40+
Chunk& new_chunk) const override;
41+
};
42+
43+
class LocalConstLessOptimisation : public PeepholeOptimisation {
44+
public:
45+
bool matches(const Chunk& chunk, const ChunkInfo&,
46+
size_t offset) const override;
47+
size_t match_length() const override;
48+
std::pair<size_t, size_t> emit(const Chunk& old_chunk, size_t old_offset,
49+
Chunk& new_chunk) const override;
50+
};
51+
2852
size_t next_instruction(const Chunk& chunk, size_t offset);
2953

3054
Chunk peephole_optimise(Chunk& chunk);

src/vm.cpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -636,7 +636,25 @@ InterpretResult VM::run() {
636636
set_local_variable(local_index, result);
637637
DISPATCH();
638638
}
639-
DO_NOP: { DISPATCH(); }
639+
DO_LOCAL_CONST_LESS: {
640+
uint8_t local_index = *local_ip++;
641+
#ifdef LOX_DEBUG
642+
if (static_cast<size_t>(local_index) > stack.size()) {
643+
std::cerr << "stack_size=" << stack.size()
644+
<< ", local_index=" << +local_index << "\n";
645+
error("ADD_LOCAL_CONST: invalid local variable index");
646+
}
647+
#endif
648+
lox::Value local_value = get_local_variable(local_index);
649+
uint8_t constant_index = *local_ip++;
650+
lox::Value cnst = chunkptr->constant_at(constant_index);
651+
if (lox::is_double(local_value) && lox::is_double(cnst)) {
652+
lox::Value result =
653+
lox::from_bool(lox::as_double(local_value) < lox::as_double(cnst));
654+
stack_push(result);
655+
}
656+
DISPATCH();
657+
}
640658
DO_JUMP_IF_FALSE: {
641659
// Don't pop the condition yet, because we might need to use it for
642660
// logical shortcircuiting later.

test.lox

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
fun f() {
22
var s = 0;
33
for (var i = 0; i < 1000000; i = i + 1) {
4-
s = s + i;
4+
s = s + 2;
55
}
66
return s;
77
}

0 commit comments

Comments
 (0)