Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions headers/gensym/defs.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ using Id = int;
using Addr = unsigned int;
using IntData = int64_t;
using UIntData = unsigned long long int;
using FloatData = long double;
using Fd = int;
using status_t = unsigned short;

Expand Down
114 changes: 67 additions & 47 deletions headers/gensym/value_ops.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -265,26 +265,51 @@ inline char proj_IntV_char(const PtrVal& v) {
return static_cast<char>(proj_IntV(intV));
}

/* Concrete floating-point values are kept in their native representation;
* `bw` selects which member of the union is the live one.
*/
union FloatV_val {
float f32;
double f64;
long double f80;
FloatV_val(FloatData f, size_t bw) {
switch (bw) {
case 32: f32 = f; break;
case 64: f64 = f; break;
case 80: f80 = f; break;
default: ABORT("Making a float of unsupported bitwidth " << bw);
}
}
};

struct FloatV : Value {
long double f;
FloatV_val f;
size_t bw;
FloatV(long double f, size_t bw=32) : f(f), bw(bw) {
FloatV(FloatData f, size_t bw=32) : f(f, bw), bw(bw) {
hash_combine(hash(), std::string("floatv"));
hash_combine(hash(), f);
hash_combine(hash(), as_ld());
hash_combine(hash(), bw);
}
FloatV(const FloatV& v): FloatV(v.f, v.bw) {}
FloatV(const FloatV& v) : FloatV(v.as_ld(), v.bw) {}
std::string toString() const override {
std::ostringstream ss;
ss << "FloatV(f=" << f << ", bw=" << bw << ")";
ss << "FloatV(f=" << as_ld() << ", bw=" << bw << ")";
return ss.str();
}
virtual bool is_conc() const override { return true; }
virtual size_t get_bw() const override { return bw; }
FloatData as_ld() const {
switch (bw) {
case 32: return f.f32;
case 64: return f.f64;
case 80: return f.f80;
}
ABORT("Reading a float of unsupported bitwidth " << bw);
}

virtual bool compare(const Value* v) const override {
auto that = static_cast<decltype(this)>(v);
if (this->f != that->f) return false;
if (this->as_ld() != that->as_ld()) return false;
return this->bw == that->bw;
}
virtual List<PtrVal> to_bytes() {
Expand All @@ -295,23 +320,19 @@ struct FloatV : Value {
}
};

inline PtrVal make_FloatV(long double f) {
auto ret = make_simple<FloatV>(f);
return hashconsing(ret);
}

inline PtrVal make_FloatV(long double f, size_t bw) {
inline PtrVal make_FloatV(FloatData f, size_t bw=32) {
auto ret = make_simple<FloatV>(f, bw);
return hashconsing(ret);
}

inline PtrVal make_FloatV_fp80(std::array<unsigned char, 10> buf) {
//std::cout << "*(long double*)buf.data(): " << *(long double*) buf.data() << std::endl;
return make_FloatV(*(long double*) buf.data(), 80);
long double f = 0;
std::memcpy(&f, buf.data(), buf.size());
return make_FloatV(f, 80);
}

inline long double proj_FloatV(PtrVal v) {
return v->to_FloatV()->f;
inline FloatData proj_FloatV(const PtrVal& v) {
return v->to_FloatV()->as_ld();
}

inline PtrVal ui_tofp(PtrVal v) {
Expand All @@ -323,13 +344,12 @@ inline PtrVal ui_tofp(PtrVal v) {
inline PtrVal fp_toui(PtrVal v, size_t bw) {
auto fp = v->to_FloatV();
ASSERT(fp != nullptr, "value passed to fp_toui is not a FloatV");
return make_IntV((uint64_t)fp->f, bw);
return make_IntV((uint64_t)fp->as_ld(), bw);
}

inline PtrVal fp_tosi(const PtrVal& v, size_t bw) {
auto fp = v->to_FloatV();
ASSERT(fp != nullptr, "value passed to fp_tosi is not a FloatV");
return make_IntV(fp->f, bw);
ASSERT(v->to_FloatV() != nullptr, "value passed to fp_tosi is not a FloatV");
return make_IntV(proj_FloatV(v), bw);
}

inline PtrVal si_tofp(const PtrVal& v) {
Expand Down Expand Up @@ -892,52 +912,52 @@ inline PtrVal float_op_2(fOP op, const PtrVal& v1, const PtrVal& v2) {
}

if (f1 && f2) {
auto apply = [&](auto x, auto y) -> PtrVal {
switch (op) {
case fOP::op_fadd: return make_FloatV(f1->f + f2->f, MAX(f1->bw, f2->bw));
case fOP::op_fsub: return make_FloatV(f1->f - f2->f, MAX(f1->bw, f2->bw));
case fOP::op_fmul: return make_FloatV(f1->f * f2->f, MAX(f1->bw, f2->bw));
case fOP::op_fdiv: return make_FloatV(f1->f / f2->f, MAX(f1->bw, f2->bw));
case fOP::op_oeq: return make_IntV(f1->f == f2->f, 1);
case fOP::op_ogt: return make_IntV(f1->f > f2->f, 1);
case fOP::op_oge: return make_IntV(f1->f >= f2->f, 1);
case fOP::op_olt: return make_IntV(f1->f < f2->f, 1);
case fOP::op_ole: return make_IntV(f1->f <= f2->f, 1);
case fOP::op_one: return make_IntV(f1->f != f2->f, 1);
case fOP::op_ueq: return make_IntV(f1->f == f2->f, 1);
case fOP::op_ugt: return make_IntV(f1->f > f2->f, 1);
case fOP::op_uge: return make_IntV(f1->f >= f2->f, 1);
case fOP::op_ult: return make_IntV(f1->f < f2->f, 1);
case fOP::op_ule: return make_IntV(f1->f <= f2->f, 1);
case fOP::op_une: return make_IntV(f1->f != f2->f, 1);
case fOP::op_fadd: return make_FloatV(x + y, bw1);
case fOP::op_fsub: return make_FloatV(x - y, bw1);
case fOP::op_fmul: return make_FloatV(x * y, bw1);
case fOP::op_fdiv: return make_FloatV(x / y, bw1);
case fOP::op_oeq: return make_IntV(x == y, 1);
case fOP::op_ogt: return make_IntV(x > y, 1);
case fOP::op_oge: return make_IntV(x >= y, 1);
case fOP::op_olt: return make_IntV(x < y, 1);
case fOP::op_ole: return make_IntV(x <= y, 1);
case fOP::op_one: return make_IntV(x != y, 1);
case fOP::op_ueq: return make_IntV(x == y, 1);
case fOP::op_ugt: return make_IntV(x > y, 1);
case fOP::op_uge: return make_IntV(x >= y, 1);
case fOP::op_ult: return make_IntV(x < y, 1);
case fOP::op_ule: return make_IntV(x <= y, 1);
case fOP::op_une: return make_IntV(x != y, 1);
/* TODO: QNAN <2022-03-10, David Deng> */
case fOP::op_ord: return make_IntV(1);
case fOP::op_uno: return make_IntV(0);
case fOP::const_false: return make_IntV(0, 1);
case fOP::const_true: return make_IntV(1, 1);
}
ABORT("Unknown float_op_2 operation");
ABORT("Unknown float_op_2 operation");};
switch (bw1) {
case 32: return apply(f1->f.f32, f2->f.f32);
case 64: return apply(f1->f.f64, f2->f.f64);
case 80: return apply(f1->f.f80, f2->f.f80);
}
ABORT("Operating on a float of unsupported bitwidth " << bw1);
} else {
ABORT("Non-concrete Float Detected");
}
}
}

/* TODO: implement those two <2022-03-10, David Deng> */

inline PtrVal fp_ext(const PtrVal& v1, int from, int to) {
auto f1 = v1->to_FloatV();
ASSERT((f1 != nullptr), "extending a non-FloatV value");
return make_FloatV(f1->f, to);
return make_FloatV(proj_FloatV(v1), to);
}

inline PtrVal fp_trunc(const PtrVal& v1, int from, int to) {
auto f1 = v1->to_FloatV();
ASSERT((f1 != nullptr), "truncating a non-FloatV value");
/* TODO: support other bw values (e.g. fp80) <2022-03-10, David Deng> */
switch (to) {
case 32: return make_FloatV(float(f1->f), to);
case 64: return make_FloatV(double(f1->f), to);
default: return make_FloatV(f1->f, to);
}
return make_FloatV(proj_FloatV(v1), to);
}

inline std::string ptrval_to_string(const PtrVal& ptr) {
Expand Down
5 changes: 4 additions & 1 deletion headers/test/Makefile
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
FLAGS := -I ../ -I ../../third-party/immer -I ../../third-party/parallel-hashmap -I ../../third-party/stp/build/include/ -L ../../third-party/stp/build/lib/ -lstp -fPIC

targets = fact_lms fact_plain sym_test conc_test stp_test fs_test external_test coverage_test
targets = fact_lms fact_plain sym_test conc_test stp_test fs_test external_test coverage_test float_test

all: $(targets)

float_test: float_test.cpp ../gensym/value_ops.hpp
g++ -std=c++17 float_test.cpp -o float_test $(FLAGS)

coverage_test: coverage_test.cpp ../gensym.hpp
g++ -std=c++17 coverage_test.cpp -o coverage_test $(FLAGS) -pthread

Expand Down
171 changes: 171 additions & 0 deletions headers/test/float_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
#include <bits/stdc++.h>
#include <sys/resource.h>
#include <parallel_hashmap/phmap.h>

#include <gensym/immeralgo.hpp>
#include <gensym/auxiliary.hpp>
#include <gensym/defs.hpp>
#include <gensym/value_ops.hpp>

namespace {

const long double pi = 3.14159265358979323846L;

template <typename T>
void test_ops(T x, T y, size_t bw) {
const PtrVal lhs = make_FloatV(x, bw), rhs = make_FloatV(y, bw);
assert(lhs->is_conc() && lhs->get_bw() == bw && lhs->get_byte_size() == bw/8);
assert(lhs->to_bytes().size() == bw/8);
assert(proj_FloatV(lhs) == x && proj_FloatV(rhs) == y);
assert(lhs == make_FloatV(x, bw));

assert(proj_FloatV(float_op_2(fOP::op_fadd, lhs, rhs)) == (T)(x + y));
assert(proj_FloatV(float_op_2(fOP::op_fsub, lhs, rhs)) == (T)(x - y));
assert(proj_FloatV(float_op_2(fOP::op_fmul, lhs, rhs)) == (T)(x * y));
assert(proj_FloatV(float_op_2(fOP::op_fdiv, lhs, rhs)) == (T)(x / y));

const std::pair<fOP, bool> cmps[] = {
{ fOP::op_oeq, x == y }, { fOP::op_ogt, x > y }, { fOP::op_oge, x >= y },
{ fOP::op_olt, x < y }, { fOP::op_ole, x <= y }, { fOP::op_one, x != y },
{ fOP::op_ueq, x == y }, { fOP::op_ugt, x > y }, { fOP::op_uge, x >= y },
{ fOP::op_ult, x < y }, { fOP::op_ule, x <= y }, { fOP::op_une, x != y },
{ fOP::op_ord, !std::isnan(x) && !std::isnan(y) },
{ fOP::op_uno, std::isnan(x) || std::isnan(y) },
};
for (auto [op, expected] : cmps) {
const PtrVal c = float_op_2(op, lhs, rhs);
assert(c->get_bw() == (op == fOP::op_ord || op == fOP::op_uno ? default_bw : 1));
assert(proj_IntV(c) == expected);
}
}

void test_f80() {
std::cout << "f80..." << std::endl;
// Round-trip keeps full long double precision, including extremes.
const long double vals[] = { 0.0L, -0.0L, 1.0L, -1.0L, 0.1L, 1.0L/3.0L,
pi, LDBL_EPSILON, LDBL_MAX, -LDBL_MAX, LDBL_TRUE_MIN };
for (long double x : vals) assert(proj_FloatV(make_FloatV(x, 80)) == x);

// Construction from the memory representation.
assert(proj_FloatV(make_FloatV_fp80({0,0,0,0,0,0,0,0x80,0xFF,0x3F})) == 1.0L);
std::array<unsigned char, 10> buf;
std::memcpy(buf.data(), &pi, 10);
assert(proj_FloatV(make_FloatV_fp80(buf)) == pi);
assert(make_FloatV_fp80(buf) == make_FloatV(pi, 80));

test_ops(1.5L, 0.5L, 80);
test_ops(0.1L, 0.2L, 80);
test_ops(1.0L/3.0L, 3.0L, 80);
test_ops(LDBL_TRUE_MIN, LDBL_MAX, 80);

// Float-to-int conversions truncate toward zero.
assert(proj_IntV(fp_toui(make_FloatV(42.9L, 80), 64)) == 42);
assert(proj_IntV(fp_tosi(make_FloatV(-42.9L, 80), 64)) == -42);
assert(proj_FloatV(ui_tofp(make_IntV(5, 64))) == 5.0L);
assert(proj_FloatV(si_tofp(make_IntV(-5, 64))) == -5.0L);
}

void test_f32() {
std::cout << "f32..." << std::endl;
const float vals[] = { 0.0f, -0.0f, 1.0f, 0.1f, 1.0f/3.0f, FLT_EPSILON,
FLT_MAX, -FLT_MAX, FLT_TRUE_MIN };
for (float x : vals) assert(proj_FloatV(make_FloatV(x, 32)) == x);

assert(proj_FloatV(make_FloatV(0.1L, 32)) == 0.1f);
assert(make_FloatV(0.1L, 32) == make_FloatV(0.1f, 32));

test_ops(1.5f, 0.5f, 32);
test_ops(0.1f, 0.2f, 32);
test_ops(1.0f/3.0f, 3.0f, 32);
test_ops(16777216.0f, 1.0f, 32);
test_ops(FLT_TRUE_MIN, FLT_MAX, 32);

// test arithmetic executions in float precision
assert(proj_FloatV(float_op_2(fOP::op_fadd, make_FloatV(1.0L, 32), make_FloatV(1e-8L, 32))) == 1.0f);
assert(proj_IntV(fp_toui(make_FloatV(2.5f, 32), 32)) == 2);
assert(proj_IntV(fp_tosi(make_FloatV(-2.5f, 32), 32)) == -2);
}

void test_f64() {
std::cout << "f64..." << std::endl;
const double vals[] = { 0.0, -0.0, 1.0, 0.1, 1.0/3.0, DBL_EPSILON,
DBL_MAX, -DBL_MAX, DBL_TRUE_MIN };
for (double x : vals) assert(proj_FloatV(make_FloatV(x, 64)) == x);

assert(proj_FloatV(make_FloatV(0.1L, 64)) == 0.1);
assert(proj_FloatV(make_FloatV(0.1L, 64)) != 0.1L);
assert(make_FloatV(0.1L, 64) == make_FloatV(0.1, 64));

test_ops(1.5, 0.5, 64);
test_ops(0.1, 0.2, 64);
test_ops(1.0/3.0, 3.0, 64);
test_ops(DBL_TRUE_MIN, DBL_MAX, 64);

assert(proj_FloatV(float_op_2(fOP::op_fadd, make_FloatV(1.0L, 64), make_FloatV(1e-17L, 64))) == 1.0);
assert(proj_FloatV(float_op_2(fOP::op_fdiv,
make_FloatV(327.8082191780822, 64), make_FloatV(2.9489999999999998, 64)))
== 327.8082191780822 / 2.9489999999999998);

assert(proj_IntV(fp_toui(make_FloatV(2.5, 64), 64)) == 2);
assert(proj_IntV(fp_tosi(make_FloatV(-2.5, 64), 64)) == -2);
}

void test_cross_width() {
std::cout << "cross-width..." << std::endl;
const PtrVal f32pi = make_FloatV((float)pi, 32);
const PtrVal f64pi = make_FloatV((double)pi, 64);
const PtrVal f80pi = make_FloatV(pi, 80);

// fp_ext widens losslessly; fp_trunc rounds to the narrower format.
assert(fp_ext(f32pi, 32, 64)->get_bw() == 64
&& proj_FloatV(fp_ext(f32pi, 32, 64)) == (double)(float)pi);
assert(proj_FloatV(fp_ext(f32pi, 32, 80)) == (float)pi);
assert(proj_FloatV(fp_ext(f64pi, 64, 80)) == (double)pi);
assert(proj_FloatV(fp_trunc(f80pi, 80, 64)) == (double)pi);
assert(proj_FloatV(fp_trunc(f80pi, 80, 32)) == (float)pi);
assert(fp_trunc(f64pi, 64, 32)->get_bw() == 32
&& proj_FloatV(fp_trunc(f64pi, 64, 32)) == (float)pi);
assert(proj_FloatV(fp_trunc(make_FloatV(0.1, 64), 64, 32)) == 0.1f);

assert(make_FloatV(1.5, 64) == make_FloatV(1.5, 64));
assert(make_FloatV(1.5, 64) != make_FloatV(1.5, 32));
assert(*make_FloatV(0.0, 64) == *make_FloatV(-0.0, 64));
}

void test_special_values() {
std::cout << "special values..." << std::endl;
use_hashcons = false;
for (size_t bw : {32, 64, 80}) {
const PtrVal one = make_FloatV(1.0L, bw), zero = make_FloatV(0.0L, bw);
const PtrVal nzero = make_FloatV(-0.0L, bw);
const PtrVal inf = float_op_2(fOP::op_fdiv, one, zero);
const PtrVal neg_inf = float_op_2(fOP::op_fdiv, make_FloatV(-1.0L, bw), zero);
const PtrVal nan = float_op_2(fOP::op_fdiv, zero, zero);
assert(std::isinf(proj_FloatV(inf)));
assert(std::isinf(proj_FloatV(neg_inf)) && std::signbit(proj_FloatV(neg_inf)));
assert(std::isnan(proj_FloatV(nan)));
assert(std::isnan(proj_FloatV(float_op_2(fOP::op_fsub, inf, inf))));
assert(std::isinf(proj_FloatV(float_op_2(fOP::op_fadd, inf, inf))));
assert(proj_IntV(float_op_2(fOP::op_ogt, inf, one)) == 1);
assert(proj_IntV(float_op_2(fOP::op_oeq, nan, nan)) == 0);
assert(proj_IntV(float_op_2(fOP::op_ogt, nan, nan)) == 0);
assert(proj_IntV(float_op_2(fOP::op_one, nan, nan)) == 1);
assert(proj_IntV(float_op_2(fOP::op_oeq, nzero, zero)) == 1);
assert(std::signbit(proj_FloatV(float_op_2(fOP::op_fmul, nzero, one))));
assert(!std::signbit(proj_FloatV(float_op_2(fOP::op_fadd, nzero, zero))));
assert(std::isnan(proj_FloatV(float_op_2(fOP::op_fdiv, nzero, zero))));
}
use_hashcons = true;
}

}

int main() {
test_f80();
test_f32();
test_f64();
test_cross_width();
test_special_values();
std::cout << "All float tests passed." << std::endl;
return 0;
}