Skip to content

Commit 7cb6b6d

Browse files
committed
improve static code analysis and validation
1 parent 4fc9095 commit 7cb6b6d

File tree

15 files changed

+256
-44
lines changed

15 files changed

+256
-44
lines changed

.github/workflows/full_test.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ jobs:
1818
run: |
1919
mkdir -p $HOME/loda
2020
git clone https://github.com/loda-lang/loda-programs.git $HOME/loda/programs
21-
- name: Test IE
22-
run: ./loda test-ie
21+
- name: Test IncEval
22+
run: ./loda test-inceval
23+
- name: Test LogEval
24+
run: ./loda test-logeval
2325
- name: Test PARI
2426
run: |
2527
sudo apt-get update

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@
7676
"vector": "cpp",
7777
"algorithm": "cpp",
7878
"filesystem": "cpp",
79-
"queue": "cpp"
79+
"queue": "cpp",
80+
"functional": "cpp"
8081
},
8182
"makefile.makeDirectory": "src"
8283
}

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@ To install or update LODA, please follow the [installation instructions](https:/
22

33
## [Unreleased]
44

5+
## v22.12.14
6+
7+
### Bugfixes
8+
9+
* Fix validation for IE programs
10+
* Use transitive hash values for programs with `seq`
11+
12+
### Enhancements
13+
14+
* Add detection of programs with logarithmic complexity
15+
* Add stats for incremental and logarithmic eval
16+
517
## v22.12.9
618

719
### Bugfixes

src/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ ifdef LODA_PLATFORM
88
CXXFLAGS += -DLODA_PLATFORM=$(LODA_PLATFORM)
99
endif
1010

11-
OBJS = api_client.o benchmark.o big_number.o blocks.o boinc.o commands.o config.o distribution.o evaluator.o evaluator_inc.o expression.o expression_util.o extender.o external/jute.o file.o finder.o formula.o formula_gen.o generator.o generator_v1.o generator_v2.o generator_v3.o generator_v4.o generator_v5.o generator_v6.o generator_v7.o interpreter.o iterator.o log.o main.o matcher.o memory.o metrics.o miner.o minimizer.o mutator.o number.o oeis_list.o oeis_manager.o oeis_sequence.o optimizer.o pari.o parser.o process.o program.o program_util.o reducer.o semantics.o setup.o sequence.o stats.o test.o util.o web_client.o
11+
OBJS = api_client.o benchmark.o big_number.o blocks.o boinc.o commands.o config.o distribution.o evaluator.o evaluator_inc.o evaluator_log.o expression.o expression_util.o extender.o external/jute.o file.o finder.o formula.o formula_gen.o generator.o generator_v1.o generator_v2.o generator_v3.o generator_v4.o generator_v5.o generator_v6.o generator_v7.o interpreter.o iterator.o log.o main.o matcher.o memory.o metrics.o miner.o minimizer.o mutator.o number.o oeis_list.o oeis_manager.o oeis_sequence.o optimizer.o pari.o parser.o process.o program.o program_util.o reducer.o semantics.o setup.o sequence.o stats.o test.o util.o web_client.o
1212

1313
loda: external/jute.h external/jute.cpp $(OBJS)
1414
$(CXX) $(LDFLAGS) -o loda $(OBJS)

src/Makefile.windows.mk

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ CXXFLAGS = $(CXXFLAGS) /Z7
1010
CXXFLAGS = $(CXXFLAGS) /DLODA_PLATFORM=$(LODA_PLATFORM)
1111
!ENDIF
1212

13-
SRCS = api_client.cpp benchmark.cpp big_number.cpp blocks.cpp boinc.cpp commands.cpp config.cpp distribution.cpp evaluator.cpp evaluator_inc.cpp expression.cpp expression_util.cpp extender.cpp external/jute.cpp file.cpp finder.cpp formula.cpp formula_gen.cpp generator.cpp generator_v1.cpp generator_v2.cpp generator_v3.cpp generator_v4.cpp generator_v5.cpp generator_v6.cpp generator_v7.cpp interpreter.cpp iterator.cpp log.cpp main.cpp matcher.cpp memory.cpp metrics.cpp miner.cpp minimizer.cpp mutator.cpp number.cpp oeis_list.cpp oeis_manager.cpp oeis_sequence.cpp optimizer.cpp pari.cpp parser.cpp process.cpp program.cpp program_util.cpp reducer.cpp semantics.cpp sequence.cpp setup.cpp stats.cpp test.cpp util.cpp web_client.cpp
13+
SRCS = api_client.cpp benchmark.cpp big_number.cpp blocks.cpp boinc.cpp commands.cpp config.cpp distribution.cpp evaluator.cpp evaluator_inc.cpp evaluator_log.cpp expression.cpp expression_util.cpp extender.cpp external/jute.cpp file.cpp finder.cpp formula.cpp formula_gen.cpp generator.cpp generator_v1.cpp generator_v2.cpp generator_v3.cpp generator_v4.cpp generator_v5.cpp generator_v6.cpp generator_v7.cpp interpreter.cpp iterator.cpp log.cpp main.cpp matcher.cpp memory.cpp metrics.cpp miner.cpp minimizer.cpp mutator.cpp number.cpp oeis_list.cpp oeis_manager.cpp oeis_sequence.cpp optimizer.cpp pari.cpp parser.cpp process.cpp program.cpp program_util.cpp reducer.cpp semantics.cpp sequence.cpp setup.cpp stats.cpp test.cpp util.cpp web_client.cpp
1414

1515
loda: external/jute.h external/jute.cpp $(SRCS)
1616
cl /EHsc /Feloda.exe $(CXXFLAGS) $(SRCS)

src/commands.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
#include "commands.hpp"
22

3+
#include <fstream>
34
#include <iostream>
45

56
#include "benchmark.hpp"
67
#include "boinc.hpp"
78
#include "evaluator.hpp"
89
#include "evaluator_inc.hpp"
10+
#include "evaluator_log.hpp"
911
#include "formula_gen.hpp"
1012
#include "iterator.hpp"
1113
#include "log.hpp"
@@ -317,6 +319,32 @@ void Commands::testIncEval() {
317319
std::to_string(count) + " programs");
318320
}
319321

322+
void Commands::testLogEval() {
323+
initLog(false);
324+
Log::get().info("Testing logarithmic evaluator");
325+
Parser parser;
326+
OeisManager manager(settings);
327+
auto& stats = manager.getStats();
328+
int64_t count = 0;
329+
for (size_t id = 0; id < stats.all_program_ids.size(); id++) {
330+
if (!stats.all_program_ids[id]) {
331+
continue;
332+
}
333+
OeisSequence seq(id);
334+
std::ifstream in(seq.getProgramPath());
335+
if (!in) {
336+
continue;
337+
}
338+
auto program = parser.parse(in);
339+
if (LogarithmicEvaluator::hasLogarithmicComplexity(program)) {
340+
Log::get().info(seq.id_str() + " has logarithmic complexity");
341+
count++;
342+
}
343+
}
344+
Log::get().info(std::to_string(count) +
345+
" programs have logarithmic complexity");
346+
}
347+
320348
void Commands::testPari() {
321349
initLog(false);
322350
Parser parser;

src/evaluator_log.cpp

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#include "evaluator_log.hpp"
2+
3+
#include "program_util.hpp"
4+
5+
bool LogarithmicEvaluator::hasLogarithmicComplexity(const Program& program) {
6+
// split up the program into fragments:
7+
Program pre_loop, loop_body;
8+
int64_t phase = 0;
9+
int64_t loop_counter_cell = 0;
10+
for (auto& op : program.ops) {
11+
if (op.type == Operation::Type::NOP) {
12+
continue;
13+
}
14+
// check for forbidden operation/operand types
15+
if (op.type == Operation::Type::SEQ || op.type == Operation::Type::CLR ||
16+
ProgramUtil::hasIndirectOperand(op)) {
17+
return false;
18+
}
19+
if (op.type == Operation::Type::LPB) {
20+
if (phase != 0 || op.target.type != Operand::Type::DIRECT ||
21+
op.source != Operand(Operand::Type::CONSTANT, 1)) {
22+
return false;
23+
}
24+
loop_counter_cell = op.target.value.asInt();
25+
phase = 1;
26+
continue;
27+
}
28+
if (op.type == Operation::Type::LPE) {
29+
if (phase != 1) {
30+
return false;
31+
}
32+
phase = 2;
33+
continue;
34+
}
35+
if (phase == 0) {
36+
pre_loop.ops.push_back(op);
37+
} else if (phase == 1) {
38+
loop_body.ops.push_back(op);
39+
}
40+
}
41+
42+
// need to be in the post-loop phase here for success
43+
if (phase != 2) {
44+
return false;
45+
}
46+
47+
// static code analysis of the pre-loop fragment
48+
for (auto& op : pre_loop.ops) {
49+
// exponentiation allowed only for constant exponents, because
50+
// it could result in exponential growth of the loop counter
51+
if (op.type == Operation::Type::POW &&
52+
op.source.type != Operand::Type::CONSTANT) {
53+
return false;
54+
}
55+
}
56+
57+
// check updates of loop counter cell in loop body
58+
bool loop_counter_updated = false;
59+
for (auto& op : loop_body.ops) {
60+
const auto target = op.target.value.asInt();
61+
if (target == loop_counter_cell) {
62+
// loop counter must be updated using division
63+
if (op.type == Operation::Type::DIV || op.type == Operation::Type::DIF) {
64+
loop_counter_updated = true;
65+
} else if (op.type != Operation::Type::SUB &&
66+
op.type != Operation::Type::TRN) {
67+
// more updates using subtraction are ok, but nothing else
68+
return false;
69+
}
70+
// all updates must be using a constant argument
71+
if (op.source.type != Operand::Type::CONSTANT) {
72+
return false;
73+
}
74+
}
75+
}
76+
if (!loop_counter_updated) {
77+
return false;
78+
}
79+
80+
// success: program has log complexity
81+
return true;
82+
}

src/finder.cpp

Lines changed: 70 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
#include <fstream>
44
#include <iomanip>
5+
#include <set>
56
#include <sstream>
67

78
#include "config.hpp"
9+
#include "evaluator_log.hpp"
810
#include "file.hpp"
911
#include "log.hpp"
1012
#include "number.hpp"
@@ -215,7 +217,7 @@ std::pair<std::string, Program> Finder::checkProgramBasic(
215217
num_default_terms);
216218
}
217219
// compare with hash of existing program
218-
if (previous_hash != ProgramUtil::hash(existing)) {
220+
if (previous_hash != getTransitiveProgramHash(existing)) {
219221
Log::get().debug("Skipping update of " + seq.id_str() +
220222
" because of hash mismatch");
221223
return result;
@@ -265,6 +267,21 @@ bool isBetterIndirectMemory(const Program &existing, const Program &optimized) {
265267
!ProgramUtil::hasOp(optimized, Operation::Type::SEQ));
266268
}
267269

270+
bool isBetterIncEval(const Program &existing, const Program &optimized,
271+
Evaluator &evaluator) {
272+
return (ProgramUtil::hasOp(existing, Operation::Type::LPB) &&
273+
(ProgramUtil::hasOp(existing, Operation::Type::SEQ) ||
274+
!ProgramUtil::hasOp(optimized, Operation::Type::SEQ)) &&
275+
!evaluator.supportsIncEval(existing) &&
276+
evaluator.supportsIncEval(optimized));
277+
}
278+
279+
bool isBetterLogEval(const Program &existing, const Program &optimized) {
280+
return (ProgramUtil::hasOp(existing, Operation::Type::LPB) &&
281+
!LogarithmicEvaluator::hasLogarithmicComplexity(existing) &&
282+
LogarithmicEvaluator::hasLogarithmicComplexity(optimized));
283+
}
284+
268285
std::string Finder::isOptimizedBetter(Program existing, Program optimized,
269286
const OeisSequence &seq) {
270287
static const std::string not_better;
@@ -295,17 +312,18 @@ std::string Finder::isOptimizedBetter(Program existing, Program optimized,
295312
return not_better;
296313
}
297314

298-
// check if the optimized program supports IE
299-
if (ProgramUtil::hasOp(existing, Operation::Type::LPB) &&
300-
(ProgramUtil::hasOp(existing, Operation::Type::SEQ) ||
301-
!ProgramUtil::hasOp(optimized, Operation::Type::SEQ))) {
302-
const bool inc_eval_existing = evaluator.supportsIncEval(existing);
303-
const bool inc_eval_optimized = evaluator.supportsIncEval(optimized);
304-
if (inc_eval_optimized && !inc_eval_existing) {
305-
return "Faster (IE)";
306-
} else if (!inc_eval_optimized && inc_eval_existing) {
307-
return not_better; // worse
308-
}
315+
// check if the optimized program has logarithmic complexity
316+
if (isBetterLogEval(existing, optimized)) {
317+
return "Faster (log)";
318+
} else if (isBetterLogEval(optimized, existing)) {
319+
return not_better; // worse
320+
}
321+
322+
// check if the optimized program supports incremental evaluation
323+
if (isBetterIncEval(existing, optimized, evaluator)) {
324+
return "Faster (IE)";
325+
} else if (isBetterIncEval(optimized, existing, evaluator)) {
326+
return not_better; // worse
309327
}
310328

311329
// check if there are loops with contant number of iterations involved
@@ -324,6 +342,8 @@ std::string Finder::isOptimizedBetter(Program existing, Program optimized,
324342
} else if (optimized_bad_count > existing_bad_count) {
325343
return not_better; // worse
326344
}
345+
346+
// check indirect memory
327347
if (isBetterIndirectMemory(existing, optimized)) {
328348
return "Simpler";
329349
} else if (isBetterIndirectMemory(optimized, existing)) {
@@ -364,13 +384,13 @@ std::string Finder::isOptimizedBetter(Program existing, Program optimized,
364384
const auto existing_steps = evaluator.eval(existing, tmp, num_terms, false);
365385

366386
// compare number of successfully computed terms
367-
double existing_terms = existing_steps.runs;
368-
double optimized_terms = optimized_steps.runs;
369-
if (optimized_terms > (existing_terms * THRESHOLD_BETTER)) {
370-
return "Better";
371-
} else if (existing_terms > (optimized_terms * THRESHOLD_BETTER)) {
372-
return not_better; // worse
373-
}
387+
// double existing_terms = existing_steps.runs;
388+
// double optimized_terms = optimized_steps.runs;
389+
// if (optimized_terms > (existing_terms * THRESHOLD_BETTER)) {
390+
// return "Better";
391+
//} else if (existing_terms > (optimized_terms * THRESHOLD_BETTER)) {
392+
// return not_better; // worse
393+
//}
374394

375395
// compare number of execution cycles
376396
double existing_total = existing_steps.total;
@@ -384,6 +404,37 @@ std::string Finder::isOptimizedBetter(Program existing, Program optimized,
384404
return not_better; // not better or worse => no change
385405
}
386406

407+
void collectPrograms(const Program &p, std::set<Program> &collected) {
408+
if (collected.find(p) != collected.end()) {
409+
return;
410+
}
411+
collected.insert(p);
412+
for (auto &op : p.ops) {
413+
if (op.type == Operation::Type::SEQ &&
414+
op.source.type == Operand::Type::CONSTANT) {
415+
auto id = op.source.value.asInt();
416+
auto path = OeisSequence(id).getProgramPath();
417+
try {
418+
Parser parser;
419+
auto p2 = parser.parse(path);
420+
collectPrograms(p2, collected);
421+
} catch (const std::exception &) {
422+
Log::get().warn("Referenced program not found: " + path);
423+
}
424+
}
425+
}
426+
}
427+
428+
size_t Finder::getTransitiveProgramHash(const Program &program) {
429+
std::set<Program> collected;
430+
collectPrograms(program, collected);
431+
size_t h = 0;
432+
for (auto &p : collected) {
433+
h += ProgramUtil::hash(p);
434+
}
435+
return h;
436+
}
437+
387438
void Finder::notifyInvalidMatch(size_t id) {
388439
if (invalid_matches.find(id) == invalid_matches.end()) {
389440
invalid_matches[id] = 1;

src/include/commands.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ class Commands {
4242

4343
void testIncEval();
4444

45+
void testLogEval();
46+
4547
void testPari();
4648

4749
void dot(const std::string& path);

src/include/evaluator_log.hpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#pragma once
2+
3+
#include "program.hpp"
4+
5+
// Logarithmic Evaluator is not a real evaluator, but a static code analysis
6+
// utility to find out whether a program consists of a loop that is executed
7+
// in O(log(n)) time complexity. This is done by inspecting the operations on
8+
// the loop counter cell and ensuring that it gets divided by a constant >1
9+
// in every iteration.
10+
//
11+
class LogarithmicEvaluator {
12+
public:
13+
static bool hasLogarithmicComplexity(const Program& program);
14+
};

0 commit comments

Comments
 (0)