/*
 * Behavioral harness for DF-4883 (dc_fixpt_log divergence / ASSERT-only
 * validation family in sys/dev/drm/amd/display/dc/basics/fixpt31_32.c).
 *
 * Function bodies below are copied VERBATIM from:
 *   sys/dev/drm/amd/display/dc/basics/fixpt31_32.c   (from_fraction, exp,
 *     taylor series, log)
 *   sys/dev/drm/amd/display/include/fixed31_32.h     (add/sub/neg/abs/lt/le,
 *     mul, div, div_int, mul_int, shl, round, constants)
 * with the following documented substitutions for userspace:
 *   - ASSERT(x)  -> no-op, exactly as in a production kernel build
 *                   (os_types.h:76 `#define ASSERT(expr) WARN_ON(!(expr))`,
 *                   WARN_ON is non-fatal in the DragonFly DRM compat layer).
 *   - div64_u64_rem/div64_s64 -> plain 64-bit / and %.
 *   - an iteration counter is added to dc_fixpt_log (HARNESS-ONLY, marked).
 *
 * Build:  cc -O2 -o fixpt_harness fixpt_harness.c
 * Run:    ./fixpt_harness
 *
 * Scenarios:
 *   [sanity] dc_fixpt_log(2.0) must converge to ~0.693 quickly.
 *   [DF-4883] dc_fixpt_log(-1.0): child is killed after TIMEOUT seconds or
 *             dies on SIGFPE (integer divide by zero, the userspace mirror
 *             of the kernel #DE panic through complete_integer_division_u64,
 *             the same sink as DF-1949).  Either outcome is a halt-class
 *             failure (unbounded loop or divide exception).
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <limits.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

#define ASSERT(x) do { } while (0)   /* production semantics */

struct fixed31_32 { long long value; };

#define FIXED31_32_BITS_PER_FRACTIONAL_PART 32

static const struct fixed31_32 dc_fixpt_zero = { 0 };
static const struct fixed31_32 dc_fixpt_half = { 0x80000000LL };
static const struct fixed31_32 dc_fixpt_one = { 0x100000000LL };
static const struct fixed31_32 dc_fixpt_two_pi = { 26986075409LL };
static const struct fixed31_32 dc_fixpt_ln2 = { 2977044471LL };
static const struct fixed31_32 dc_fixpt_ln2_div_2 = { 1488522236LL };

static unsigned long long div64_u64_rem(unsigned long long dividend,
    unsigned long long divisor, unsigned long long *remainder)
{
	*remainder = dividend % divisor;
	return dividend / divisor;
}

static long long div64_s64(long long n, long long d) { return n / d; }

static inline unsigned long long abs_i64(long long arg)	/* fixpt31_32.c:29 */
{
	if (arg > 0)
		return (unsigned long long)arg;
	else
		return (unsigned long long)(-arg);
}

/* ---- begin verbatim library code ------------------------------------ */

static inline unsigned long long complete_integer_division_u64( /* :43 */
	unsigned long long dividend,
	unsigned long long divisor,
	unsigned long long *remainder)
{
	unsigned long long result;

	ASSERT(divisor);

	result = div64_u64_rem(dividend, divisor, remainder);

	return result;
}

#define FRACTIONAL_PART_MASK \
	((1ULL << FIXED31_32_BITS_PER_FRACTIONAL_PART) - 1)
#define GET_INTEGER_PART(x) \
	((x) >> FIXED31_32_BITS_PER_FRACTIONAL_PART)
#define GET_FRACTIONAL_PART(x) \
	(FRACTIONAL_PART_MASK & (x))

struct fixed31_32 dc_fixpt_from_fraction(long long numerator, long long denominator) /* :67 */
{
	struct fixed31_32 res;

	int arg1_negative = numerator < 0;
	int arg2_negative = denominator < 0;

	unsigned long long arg1_value = arg1_negative ? -numerator : numerator;
	unsigned long long arg2_value = arg2_negative ? -denominator : denominator;

	unsigned long long remainder;

	unsigned long long res_value = complete_integer_division_u64(
		arg1_value, arg2_value, &remainder);

	ASSERT(res_value <= LONG_MAX);

	{
		unsigned int i = FIXED31_32_BITS_PER_FRACTIONAL_PART;

		do {
			remainder <<= 1;
			res_value <<= 1;
			if (remainder >= arg2_value) {
				res_value |= 1;
				remainder -= arg2_value;
			}
		} while (--i != 0);
	}

	{
		unsigned long long summand = (remainder << 1) >= arg2_value;

		ASSERT(res_value <= LLONG_MAX - summand);

		res_value += summand;
	}

	res.value = (long long)res_value;

	if (arg1_negative ^ arg2_negative)
		res.value = -res.value;

	return res;
}

/* header inlines needed by exp/log (verbatim) */
static inline struct fixed31_32 dc_fixpt_from_int(long long value)
{
	struct fixed31_32 res;

	res.value = value << FIXED31_32_BITS_PER_FRACTIONAL_PART;

	return res;
}
static inline struct fixed31_32 dc_fixpt_neg(struct fixed31_32 arg)
{
	struct fixed31_32 res;
	res.value = -arg.value;
	return res;
}
static inline struct fixed31_32 dc_fixpt_abs(struct fixed31_32 arg)
{
	if (arg.value < 0)
		return dc_fixpt_neg(arg);
	else
		return arg;
}
static inline int dc_fixpt_lt(struct fixed31_32 a, struct fixed31_32 b) { return a.value < b.value; }
static inline int dc_fixpt_le(struct fixed31_32 a, struct fixed31_32 b) { return a.value <= b.value; }
static inline struct fixed31_32 dc_fixpt_add(struct fixed31_32 arg1, struct fixed31_32 arg2)
{
	struct fixed31_32 res;
	res.value = arg1.value + arg2.value;
	return res;
}
static inline struct fixed31_32 dc_fixpt_sub(struct fixed31_32 arg1, struct fixed31_32 arg2)
{
	struct fixed31_32 res;
	res.value = arg1.value - arg2.value;
	return res;
}
static inline struct fixed31_32 dc_fixpt_div_int(struct fixed31_32 arg1, long long arg2)
{
	return dc_fixpt_from_fraction(arg1.value, dc_fixpt_from_int(arg2).value);
}
static inline struct fixed31_32 dc_fixpt_div(struct fixed31_32 arg1, struct fixed31_32 arg2)
{
	return dc_fixpt_from_fraction(arg1.value, arg2.value);
}
static inline struct fixed31_32 dc_fixpt_shl(struct fixed31_32 arg, unsigned char shift)
{
	arg.value = arg.value << shift;
	return arg;
}
static inline int dc_fixpt_round(struct fixed31_32 arg)
{
	unsigned long long arg_value = arg.value > 0 ? arg.value : -arg.value;
	const long long summand = dc_fixpt_half.value;
	arg_value += summand;
	if (arg.value >= 0)
		return (int)(arg_value >> FIXED31_32_BITS_PER_FRACTIONAL_PART);
	else
		return -(int)(arg_value >> FIXED31_32_BITS_PER_FRACTIONAL_PART);
}

struct fixed31_32 dc_fixpt_mul(struct fixed31_32 arg1, struct fixed31_32 arg2) /* :119 */
{
	struct fixed31_32 res;

	int arg1_negative = arg1.value < 0;
	int arg2_negative = arg2.value < 0;

	unsigned long long arg1_value = arg1_negative ? -arg1.value : arg1.value;
	unsigned long long arg2_value = arg2_negative ? -arg2.value : arg2.value;

	unsigned long long arg1_int = GET_INTEGER_PART(arg1_value);
	unsigned long long arg2_int = GET_INTEGER_PART(arg2_value);

	unsigned long long arg1_fra = GET_FRACTIONAL_PART(arg1_value);
	unsigned long long arg2_fra = GET_FRACTIONAL_PART(arg2_value);

	unsigned long long tmp;

	res.value = arg1_int * arg2_int;
	res.value <<= FIXED31_32_BITS_PER_FRACTIONAL_PART;
	tmp = arg1_int * arg2_fra;
	res.value += tmp;
	tmp = arg2_int * arg1_fra;
	res.value += tmp;
	tmp = arg1_fra * arg2_fra;
	tmp = (tmp >> FIXED31_32_BITS_PER_FRACTIONAL_PART) +
		(tmp >= (unsigned long long)dc_fixpt_half.value);
	res.value += tmp;

	if (arg1_negative ^ arg2_negative)
		res.value = -res.value;

	return res;
}
static inline struct fixed31_32 dc_fixpt_mul_int(struct fixed31_32 arg1, int arg2)
{
	return dc_fixpt_mul(arg1, dc_fixpt_from_int(arg2));
}

static struct fixed31_32 fixed31_32_exp_from_taylor_series(struct fixed31_32 arg) /* :307 */
{
	unsigned int n = 9;

	struct fixed31_32 res = dc_fixpt_from_fraction(
		n + 2,
		n + 1);

	ASSERT(dc_fixpt_lt(arg, dc_fixpt_one));

	do
		res = dc_fixpt_add(
			dc_fixpt_one,
			dc_fixpt_div_int(
				dc_fixpt_mul(
					arg,
					res),
				n));
	while (--n != 1);

	return dc_fixpt_add(
		dc_fixpt_one,
		dc_fixpt_mul(
			arg,
			res));
}

struct fixed31_32 dc_fixpt_exp(struct fixed31_32 arg)		/* :335 */
{
	if (dc_fixpt_le(
		dc_fixpt_ln2_div_2,
		dc_fixpt_abs(arg))) {
		int m = dc_fixpt_round(
			dc_fixpt_div(
				arg,
				dc_fixpt_ln2));

		struct fixed31_32 r = dc_fixpt_sub(
			arg,
			dc_fixpt_mul_int(
				dc_fixpt_ln2,
				m));

		ASSERT(m != 0);
		ASSERT(dc_fixpt_lt(dc_fixpt_abs(r), dc_fixpt_one));

		if (m > 0)
			return dc_fixpt_shl(
				fixed31_32_exp_from_taylor_series(r),
				(unsigned char)m);
		else
			return dc_fixpt_div_int(
				fixed31_32_exp_from_taylor_series(r),
				1LL << -m);
	} else if (arg.value != 0)
		return fixed31_32_exp_from_taylor_series(arg);
	else
		return dc_fixpt_one;
}

static unsigned long long g_log_iterations;	/* HARNESS-ONLY instrumentation */

static void sigfpe_report(int sig)			/* HARNESS-ONLY */
{
	char buf[128];
	int n = snprintf(buf, sizeof(buf),
	    "[child] SIGFPE (integer 0/0) trapped after ~%llu log iterations\n",
	    g_log_iterations);
	write(2, buf, n);
	_exit(128 + sig);
}

struct fixed31_32 dc_fixpt_log(struct fixed31_32 arg)		/* :378 */
{
	struct fixed31_32 res = dc_fixpt_neg(dc_fixpt_one);
	struct fixed31_32 error;

	ASSERT(arg.value > 0);

	g_log_iterations = 0;

	do {
		struct fixed31_32 res1 = dc_fixpt_add(
			dc_fixpt_sub(
				res,
				dc_fixpt_one),
			dc_fixpt_div(
				arg,
				dc_fixpt_exp(res)));

		error = dc_fixpt_sub(
			res,
			res1);

		res = res1;
		g_log_iterations++;				/* HARNESS-ONLY */
	} while (abs_i64(error.value) > 100ULL);

	return res;
}

/* ---- end verbatim library code -------------------------------------- */

int main(void)
{
	struct fixed31_32 r;

	setvbuf(stdout, NULL, _IONBF, 0);

	/* [1] sanity: log(2) converges */
	r = dc_fixpt_log(dc_fixpt_from_int(2));
	printf("[sanity] dc_fixpt_log(2.0) = %.9f (expect ~0.693147181) after %llu iterations\n",
	    (double)r.value / 4294967296.0, g_log_iterations);

	/* [2] DF-4883: log(0) and [3] DF-4883: log(-1) — each in a forked
	 * child; a SIGFPE handler prints the iteration count at trap time. */
	{
		static const long long cases[] = { 0, -1 };
		for (unsigned ci = 0; ci < sizeof(cases)/sizeof(cases[0]); ci++) {
			fflush(stdout);
			pid_t p = fork();
			if (p == 0) {
				signal(SIGFPE, sigfpe_report);
				struct fixed31_32 a;
				a.value = cases[ci] << FIXED31_32_BITS_PER_FRACTIONAL_PART;
				r = dc_fixpt_log(a);
				printf("[DF-4883] dc_fixpt_log(%lld.0) RETURNED %.9f after %llu iterations\n",
				    cases[ci], (double)r.value / 4294967296.0,
				    g_log_iterations);
				_exit(0);
			}
			int st = 0, done = 0;
			for (int i = 0; i < 60 && !done; i++) {	/* wait ~60s */
				if (waitpid(p, &st, WNOHANG) == p)
					done = 1;
				else
					usleep(1000000);
			}
			if (done && WIFSIGNALED(st)) {
				printf("[DF-4883] log(%lld.0): child killed by signal %d "
				    "(SIGFPE=%d => integer-divide-by-zero; kernel equivalent "
				    "= #DE panic via complete_integer_division_u64, the "
				    "DF-1949 sink)\n",
				    cases[ci], WTERMSIG(st), SIGFPE);
			} else if (done && WIFEXITED(st)) {
				printf("[DF-4883] log(%lld.0): child exited status=%d\n",
				    cases[ci], WEXITSTATUS(st));
			} else {
				kill(p, SIGKILL);
				waitpid(p, &st, 0);
				printf("[DF-4883] log(%lld.0): child still looping after 60s "
				    "=> UNBOUNDED LOOP in dc_fixpt_log (kernel: wedged thread, "
				    "100%% CPU holding display state)\n", cases[ci]);
			}
		}
	}
	return 0;
}
