lean4-htt/src/tests/util/object.cpp
Leonardo de Moura 30a6a2ade8 feat(library/init/data, runtime): remove parray support from runtime, and implement them in Lean using Scala/Clojure Radix trees
The Scala/Clojure approach for persistent arrays works great with our
`reset/reuse`. We seem to be much more efficient than their
implementations because of `reset/reuse`. The new approach also seems
better than the old one implemented in the runtime, and has a few
advantages:
1- The reroot procedure used in the old approach required
synchronization for multi-threaded code, or we would need to perform
deep copies when sending `parray` objects between threads.
2- We don't need any runtime extension for the new approach.
3- The old approach used "trail lists" for undoing array updates.
This works well for bactracking search use cases, but it is bad
in use cases where we are simultaneously updating the persistent
arrays that have shared nodes.
2019-06-02 09:18:19 -07:00

751 lines
19 KiB
C++

/*
Copyright (c) 2018 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Leonardo de Moura
*/
#include <utility>
#include <random>
#include <iostream>
#include <vector>
#include "util/test.h" // <<< comment this list for performance experiments
#include "util/timeit.h"
#include "runtime/stackinfo.h"
#include "runtime/serializer.h"
#include "runtime/sstream.h"
#include "util/object_ref.h"
#include "util/array_ref.h"
#include "util/init_module.h"
#include "util/nat.h"
using namespace lean;
#define USED(x) (void)(x)
object * f(object *) {
std::cout << "executing f...\n";
return box(10);
}
void tst1() {
object_ref t(mk_thunk(alloc_closure(f, 0)));
object * r1 = thunk_get(t.raw());
object * r2 = thunk_get(t.raw());
std::cout << "thunk value: " << unbox(r1) << "\n";
std::cout << "thunk value: " << unbox(r2) << "\n";
}
static unsigned g_g_counter = 0;
object * g(object *) {
g_g_counter++;
return box(g_g_counter);
}
void tst2() {
object * c = alloc_closure(g, 0);
inc(c);
object * r1 = apply_1(c, box(0));
inc(c);
object * r2 = apply_1(c, box(0));
lean_assert(unbox(r1) == 1);
lean_assert(unbox(r2) == 2);
object_ref t(mk_thunk(c));
object * r3 = thunk_get(t.raw());
object * r4 = thunk_get(t.raw());
lean_assert(unbox(r3) == 3);
lean_assert(unbox(r4) == 3);
USED(r1); USED(r2); USED(r3); USED(r4);
}
static unsigned g_h_counter = 0;
object * h(object *) {
g_h_counter++;
return box(0);
}
/* Make sure box(0) is not mistaken by cached value has not been initialized yet.
The thunk implementation relies on the fact that nullptr is not a scalar nor a valid
Lean object. */
void tst3() {
object_ref t(mk_thunk(alloc_closure(h, 0)));
lean_assert(g_h_counter == 0);
object * r3 = thunk_get(t.raw());
lean_assert(g_h_counter == 1);
object * r4 = thunk_get(t.raw());
lean_assert(g_h_counter == 1);
lean_assert(unbox(r3) == 0);
lean_assert(unbox(r4) == 0);
USED(r3); USED(r4);
}
object * r(object *) {
return mk_string("hello world");
}
void tst4() {
object_ref t(mk_thunk(alloc_closure(r, 0)));
object * r3 = thunk_get(t.raw());
object * r4 = thunk_get(t.raw());
lean_assert(string_eq(r3, "hello world"));
lean_assert(string_eq(r4, "hello world"));
USED(r3); USED(r4);
}
void tst5() {
object_ref t(mk_thunk(alloc_closure(r, 0)));
std::ostringstream out;
serializer s(out);
object_ref o(mk_string("bla bla"));
s.write_object(o.raw());
s.write_object(t.raw());
s.write_object(t.raw());
std::istringstream in(out.str());
deserializer d(in);
d.read_object();
object * r1 = d.read_object();
object * r2 = d.read_object();
lean_assert(r1 == r2);
lean_assert(is_thunk(r1));
object * str = thunk_get(r1);
lean_assert(strcmp(string_cstr(str), "hello world") == 0);
USED(r2); USED(str);
}
unsigned g_counter = 0;
mutex g_io_mutex;
void show_msg(char const * msg) {
unique_lock<mutex> lock(g_io_mutex);
std::cout << msg;
}
object * task1_fn(object *) {
g_counter++;
show_msg("task 1 - started\n");
this_thread::sleep_for(std::chrono::milliseconds(100));
show_msg("task 1 - executed\n");
return box(10);
}
object * add_10(object * a) {
show_msg("task 2 - started\n");
this_thread::sleep_for(std::chrono::milliseconds(200));
show_msg("task 2 - executed\n");
return box(unbox(a) + 10);
}
obj_res task3_fn(obj_arg val, obj_arg) {
show_msg("task 3 - started\n");
this_thread::sleep_for(std::chrono::milliseconds(100));
show_msg("task 3 - executed\n");
return box(unbox(val)+100);
}
obj_res mk_task3_fn(obj_arg val) {
object * c = alloc_closure(task3_fn, 1);
closure_set(c, 0, val);
return mk_task(c);
}
obj_res mk_task2(b_obj_arg task1) {
inc(task1);
return task_map(alloc_closure(add_10, 0), task1);
}
obj_res mk_task3(b_obj_arg task1) {
inc_ref(task1);
return task_bind(task1, alloc_closure(mk_task3_fn, 0));
}
void tst6_core(object * task1) {
object_ref task2(mk_task2(task1));
object_ref task3(mk_task3(task1));
std::cout << "tst6 started...\n";
object * r1 = task_get(task2.raw());
object * r2 = task_get(task3.raw());
std::cout << "r1: " << unbox(r1) << "\n";
std::cout << "r2: " << unbox(r2) << "\n";
lean_assert(unbox(r1) == 20);
lean_assert(unbox(r2) == 110);
}
void tst6() {
{
scoped_task_manager m(8);
object_ref task1(mk_task(alloc_closure(task1_fn, 0)));
tst6_core(task1.raw());
}
{
scoped_task_manager m(8);
object_ref task1(task_pure(box(10)));
tst6_core(task1.raw());
}
{
scoped_task_manager m(8);
object_ref task1(thunk_pure(box(10)));
lean_assert(unbox(task_get(task1.raw())) == 10);
lean_assert(io_has_finished_core(task1.raw()));
tst6_core(task1.raw());
}
{
scoped_task_manager m(8);
object_ref task1(mk_thunk(alloc_closure(f, 0)));
lean_assert(io_has_finished_core(task1.raw()));
tst6_core(task1.raw());
}
}
obj_res task4_fn(obj_arg) {
show_msg("task 4 started...\n");
while (!io_check_interrupt_core()) {
show_msg("task 4 loop...\n");
this_thread::sleep_for(std::chrono::milliseconds(10));
}
show_msg("task 4 was interrupted...\n");
return box(1);
}
void tst7() {
scoped_task_manager m(8);
std::cout << ">> tst7 started...\n";
object_ref task4(mk_task(alloc_closure(task4_fn, 0)));
std::cout << "task4 has finished: " << io_has_finished_core(task4.raw()) << "\n";
this_thread::sleep_for(std::chrono::milliseconds(100));
show_msg("request interrupt...\n");
io_request_interrupt_core(task4.raw());
object * r = task_get(task4.raw());
std::cout << "task4 has finished after get: " << io_has_finished_core(task4.raw()) << "\n";
std::cout << "r: " << unbox(r) << "\n";
}
obj_res task5_fn(obj_arg id, obj_arg) {
show_msg((sstream() << "task 5[" << unbox(id) << "] started \n").str().c_str());
this_thread::sleep_for(std::chrono::milliseconds(10));
show_msg((sstream() << "task 5[" << unbox(id) << "] finished \n").str().c_str());
return id;
}
obj_res mk_task5(obj_arg id) {
object * c = alloc_closure(task5_fn, 1);
closure_set(c, 0, id);
return mk_task(c);
}
void tst8() {
scoped_task_manager m(8);
std::cout << ">> tst8 started...\n";
std::vector<object_ref> tasks;
for (unsigned i = 0; i < 100; i++) {
tasks.push_back(object_ref(mk_task5(box(i))));
}
unsigned i = 0;
for (object_ref const & t : tasks) {
object * r = task_get(t.raw());
lean_assert(unbox(r) == i);
i++;
}
}
obj_res loop_until_interrupt_fn(obj_arg) {
while (!io_check_interrupt_core()) {
this_thread::sleep_for(std::chrono::milliseconds(1));
}
return box(0);
}
obj_res task6_fn(obj_arg) {
show_msg("task 6 started...\n");
this_thread::sleep_for(std::chrono::milliseconds(100));
show_msg("task 6 done...\n");
return box(42);
}
obj_res mk_cons(b_obj_arg h, obj_arg t) {
object * r = alloc_cnstr(1, 2, 0);
inc(h);
cnstr_set(r, 0, h);
cnstr_set(r, 1, t);
return r;
}
void tst9() {
scoped_task_manager m(8);
std::cout << ">> tst9 started...\n";
object_ref t1(mk_task(alloc_closure(loop_until_interrupt_fn, 0)));
object_ref t2(mk_task(alloc_closure(loop_until_interrupt_fn, 0)));
object_ref t3(mk_task(alloc_closure(task6_fn, 0)));
object_ref ts(mk_cons(t1.raw(), mk_cons(t2.raw(), mk_cons(t3.raw(), box(0)))));
show_msg("invoke wait_any...\n");
object * t = io_wait_any_core(ts.raw());
show_msg("wait_any returned...\n");
object * v = task_get(t);
lean_assert(unbox(v) == 42);
io_request_interrupt_core(t1.raw());
io_request_interrupt_core(t2.raw());
task_get(t1.raw());
task_get(t2.raw());
}
void tst10() {
scoped_task_manager m(8);
std::cout << ">> tst10 started...\n";
object_ref t1(mk_task(alloc_closure(task6_fn, 0)));
{
object_ref t2(mk_task2(t1.raw()));
}
task_get(t1.raw());
}
void tst11() {
std::cout << ">> tst11 started...\n";
{
scoped_task_manager m(2);
std::vector<object_ref> tasks;
for (unsigned i = 0; i < 100; i++) {
tasks.push_back(object_ref(mk_task(alloc_closure(loop_until_interrupt_fn, 0))));
}
this_thread::sleep_for(std::chrono::milliseconds(100));
}
std::cout << "tst11 done...\n";
}
static atomic<bool> g_finished;
obj_res loop_until_interrupt_fn2(obj_arg) {
while (!io_check_interrupt_core()) {
this_thread::sleep_for(std::chrono::milliseconds(1));
}
g_finished = true;
return box(0);
}
void tst12() {
std::cout << ">> tst12 started...\n";
g_finished = false;
scoped_task_manager m(8);
{
object_ref t(mk_task(alloc_closure(loop_until_interrupt_fn2, 0)));
this_thread::sleep_for(std::chrono::milliseconds(10));
/* task t must be interrupted automatically */
}
while (g_finished) {
this_thread::sleep_for(std::chrono::milliseconds(1));
}
std::cout << "tst12 done...\n";
}
static atomic<unsigned> g_task7_counter(1);
obj_res task7_fn(obj_arg val, obj_arg) {
if (g_task7_counter % 10 == 0)
show_msg((sstream() << "task 7[" << g_task7_counter << "]\n").str().c_str());
g_task7_counter++;
this_thread::sleep_for(std::chrono::milliseconds(1));
return box(unbox(val)+1);
}
obj_res mk_task7_fn(obj_arg val) {
object * c = alloc_closure(task7_fn, 1);
closure_set(c, 0, val);
return mk_task(c);
}
obj_res mk_task7(obj_arg t) {
return task_bind(t, alloc_closure(mk_task7_fn, 0));
}
object * mul2(object * a) {
return box(unbox(a) * 2);
}
void tst13() {
scoped_task_manager m(8);
std::cout << "tst13 started ...\n";
object * curr = mk_task(alloc_closure(task1_fn, 0));
std::vector<object *> children;
for (unsigned i = 0; i < 1000; i++) {
curr = mk_task7(curr);
inc(curr);
children.push_back(task_map(alloc_closure(mul2, 0), curr));
}
inc(curr);
object * it = curr;
for (unsigned i = 0; i < 10000; i++) {
it = mk_task7(it);
}
dec(it); // it will force the 10000 tasks created above to die...
object * v = task_get(curr);
dec(curr);
show_msg((sstream() << "v: " << unbox(v) << "\n").str().c_str());
object * vc = task_get(children.back());
for (object * c : children)
dec(c);
lean_assert(unbox(v) == 1010);
lean_assert(unbox(vc) == 2020);
std::cout << g_task7_counter << "\n";
}
obj_res mk_foo(unsigned n) {
object * r = alloc_cnstr(0, 1, 0);
cnstr_set(r, 0, box(n));
return r;
}
unsigned foo_val(b_obj_arg v) {
return unbox(cnstr_get(v, 0));
}
object * mk_list(unsigned n) {
object * r = box(0);
for (unsigned i = 0; i < n; i++) {
object * new_r = alloc_cnstr(1, 2, 0);
cnstr_set(new_r, 0, box(i));
cnstr_set(new_r, 1, r);
r = new_r;
}
return r;
}
bool contains_borrow(object * l, object * v) {
if (is_scalar(l)) {
return false;
} else {
object * h = cnstr_get(l, 0);
object * t = cnstr_get(l, 1);
if (h == v) {
return true;
} else {
return contains_borrow(t, v);
}
}
}
bool contains(object * l, object * v) {
if (is_scalar(l)) {
dec(v);
return false;
} else {
object * h = cnstr_get(l, 0);
object * t = cnstr_get(l, 1);
if (!is_shared(l)) {
free_heap_obj(l);
} else {
inc(h);
inc(t);
dec_ref(l);
}
if (h == v) {
dec(h); dec(v); dec(t);
return true;
} else {
dec(h);
return contains(t, v);
}
}
}
inline object * mark_borrowed(object * o) {
if (is_scalar(o))
return o;
else
return reinterpret_cast<object*>(reinterpret_cast<uintptr_t>(o) | 0x2);
}
inline bool is_borrowed(object * o) {
return !is_scalar(o) && (reinterpret_cast<uintptr_t>(o) & 0x2) != 0;
}
inline object * get_object(object * o) {
if (is_scalar(o))
return o;
else
return reinterpret_cast<object*>((reinterpret_cast<uintptr_t>(o) >> 2) << 2);
}
bool contains_hybrid(object * l, object * v) {
bool l_b = is_borrowed(l);
object * l_obj = get_object(l);
bool v_b = is_borrowed(v);
object * v_obj = get_object(v);
if (is_scalar(l_obj)) {
if (!v_b) dec(v_obj);
return false;
} else {
object * h_obj = cnstr_get(l_obj, 0);
object * t_obj = cnstr_get(l_obj, 1);
object * t;
if (l_b) {
t = mark_borrowed(t_obj);
} else if (!is_shared(l_obj)) {
free_heap_obj(l_obj);
t = t_obj;
} else {
inc(h_obj);
inc(t_obj);
dec_ref(l_obj);
t = mark_borrowed(t_obj);
}
if (v_obj == h_obj) {
if (!l_b) dec(h_obj);
if (!l_b) dec(t_obj);
if (!v_b) dec(v_obj);
return false;
} else {
if (!l_b) dec(h_obj);
return contains_hybrid(t, v);
}
}
}
bool contains_fast_hybrid(object * l, object * v) {
if (is_scalar(l)) {
dec(v);
return false;
} else {
object * h = cnstr_get(l, 0);
object * t = cnstr_get(l, 1);
bool shared = is_shared(l);
if (!shared) {
free_heap_obj(l);
} else {
inc(h);
inc(t);
dec_ref(l);
}
if (h == v) {
dec(h); dec(v); dec(t);
return true;
} else if (!shared) {
dec(h);
return contains_fast_hybrid(t, v);
} else {
dec(h);
bool r = contains_borrow(t, v);
dec(t);
dec(v);
return r;
}
}
}
void tst17(unsigned n, unsigned sz) {
{
timeit timer(std::cout, "contains standard");
object * l = mk_list(sz);
for (unsigned i = 0; i < n; i++) {
inc(l);
contains(l, box(sz));
inc(l);
contains(l, box(sz/2));
}
dec(l);
}
{
timeit timer(std::cout, "contains borrowed");
object * l = mk_list(sz);
for (unsigned i = 0; i < n; i++) {
contains_borrow(l, box(sz));
contains_borrow(l, box(sz/2));
}
dec(l);
}
{
timeit timer(std::cout, "contains hybrid");
object * l = mk_list(sz);
for (unsigned i = 0; i < n; i++) {
inc(l);
contains_hybrid(l, box(sz));
inc(l);
contains_hybrid(l, box(sz/2));
}
dec(l);
}
{
timeit timer(std::cout, "contains fast hybrid");
object * l = mk_list(sz);
for (unsigned i = 0; i < n; i++) {
inc(l);
contains_fast_hybrid(l, box(sz));
inc(l);
contains_fast_hybrid(l, box(sz/2));
}
dec(l);
}
}
inline object * get(object * o, unsigned idx) {
object * r = cnstr_get(o, idx);
inc(r);
return r;
}
object * map_add1(object * l) {
if (is_scalar(l)) {
return l;
} else {
object * h = cnstr_get(l, 0); inc(h);
object * t = cnstr_get(l, 1); inc(t);
dec(l);
object * new_h = box(unbox(h)+1);
object * new_t = map_add1(t);
object * r = alloc_cnstr(1, 2, 0);
cnstr_set(r, 0, new_h);
cnstr_set(r, 1, new_t);
return r;
}
}
object * map_add1_reuse(object * l) {
if (is_scalar(l)) {
return l;
} else {
object * h = cnstr_get(l, 0); inc(h);
object * t = cnstr_get(l, 1); inc(t);
object * r;
if (is_shared(l)) {
dec(l);
r = alloc_cnstr(1, 2, 0);
} else {
r = l;
dec(h);
dec(t);
}
cnstr_set(r, 0, box(0));
cnstr_set(r, 1, box(0));
object * new_h = box(unbox(h)+1);
object * new_t = map_add1_reuse(t);
cnstr_set(r, 0, new_h);
cnstr_set(r, 1, new_t);
return r;
}
}
object * map_add1_reuse_opt(object * l) {
if (is_scalar(l)) {
return l;
} else {
object * h = cnstr_get(l, 0);
object * t = cnstr_get(l, 1);
object * r;
if (is_shared(l)) {
inc(h);
inc(t);
dec(l);
r = alloc_cnstr(1, 2, 0);
} else {
r = l;
}
object * new_h = box(unbox(h)+1);
object * new_t = map_add1_reuse_opt(t);
cnstr_set(r, 0, new_h);
cnstr_set(r, 1, new_t);
return r;
}
}
void tst18(unsigned n, unsigned sz) {
{
timeit timer(std::cout, "map");
object * l = mk_list(sz);
for (unsigned i = 0; i < n; i++) {
lean_assert(!is_shared(l));
l = map_add1(l);
}
dec(l);
}
{
timeit timer(std::cout, "map_reuse");
object * l = mk_list(sz);
for (unsigned i = 0; i < n; i++) {
lean_assert(!is_shared(l));
l = map_add1_reuse(l);
}
dec(l);
}
{
timeit timer(std::cout, "map_opt");
object * l = mk_list(sz);
for (unsigned i = 0; i < n; i++) {
lean_assert(!is_shared(l));
l = map_add1_reuse_opt(l);
}
dec(l);
}
}
void tst19() {
object * s1 = mk_string("hello");
object * s2 = mk_string("world");
inc_ref(s1);
object * s4 = string_append(s1, s2);
lean_assert(string_eq(s4, "helloworld"));
lean_assert(string_eq(s1, "hello"));
dec_ref(s4);
object * s5 = string_append(s1, s2);
lean_assert(string_eq(s5, "helloworld"));
object * b = mk_string("!");
object * s6 = string_append(s5, b);
lean_assert(string_eq(s6, "helloworld!"));
lean_assert(s5 == s6);
inc_ref(s6);
object * s7 = string_push(s6, '!');
lean_assert(string_eq(s6, "helloworld!"));
lean_assert(string_eq(s7, "helloworld!!"));
object * s8 = string_push(s7, 'a');
lean_assert(string_eq(s8, "helloworld!!a"));
lean_assert(s7 == s8);
dec_ref(s2); dec_ref(s6); dec_ref(b); dec_ref(s8);
}
void tst20() {
lean_assert(nat(LEAN_MAX_SMALL_NAT+1) == nat(LEAN_MAX_SMALL_NAT) + nat(1));
lean_assert(!nat(LEAN_MAX_SMALL_NAT+1).is_small());
lean_assert(nat(LEAN_MAX_SMALL_NAT).is_small());
lean_assert(nat(2) + nat(3) == nat(5));
lean_assert(!(nat(LEAN_MAX_SMALL_NAT) * nat(LEAN_MAX_SMALL_NAT)).is_small());
nat tmp = (nat(LEAN_MAX_SMALL_NAT) * nat(LEAN_MAX_SMALL_NAT))/nat(LEAN_MAX_SMALL_NAT);
lean_assert(tmp.is_small());
lean_assert(tmp == LEAN_MAX_SMALL_NAT);
lean_assert(nat(LEAN_MAX_SMALL_NAT) * nat(LEAN_MAX_SMALL_NAT) > nat(LEAN_MAX_SMALL_NAT));
}
void tst21() {
array_ref<nat> a({nat(1), nat(2), nat(3)});
lean_assert(a.size() == 3);
lean_assert(a[0] == nat(1));
for (nat const & v : a) {
std::cout << v << "\n";
}
}
int main() {
save_stack_info();
initialize_util_module();
tst1();
tst2();
tst3();
tst4();
tst5();
tst6();
tst7();
tst8();
tst9();
tst10();
tst11();
tst12();
tst13();
// tst17(40000, 3000);
tst17(400, 30);
// tst18(4000, 3000);
tst18(400, 30);
tst19();
tst20();
tst21();
finalize_util_module();
return has_violations() ? 1 : 0;
}