/* SPDX-License-Identifier: GPL-2.0-only */

#ifndef __ASSERT_H__
#define __ASSERT_H__

#include <arch/hlt.h>
#include <console/console.h>
#include <stdint.h>

/* TODO: Fix vendorcode headers to not define macros coreboot uses or to be more
   properly isolated. */
#ifdef ASSERT
#undef ASSERT
#endif

/* Do not use filenames nor line numbers on timeless builds, to preserve reproducibility */
#if ENV_TIMELESS
#define __ASSERT_FILE__ "(filenames not available on timeless builds)"
#define __ASSERT_LINE__ 404
#else
#define __ASSERT_FILE__ __FILE__
#define __ASSERT_LINE__ __LINE__
#endif

#ifndef _PORTING_H_	/* TODO: Isolate AGESA properly. */
#define __build_time_assert(x) \
	(__builtin_constant_p(x) ? ((x) ? 1 : dead_code_t(int)) : 0)
#else
#define __build_time_assert(x) 0
#endif

/* CMocka function redefinition. */
void mock_assert(const int result, const char *const expression,
		const char *const file, const int line);

#if ENV_TEST
#define MOCK_ASSERT(result, expression) \
	mock_assert((result), (expression), __ASSERT_FILE__, __ASSERT_LINE__)
#else
#define MOCK_ASSERT(result, expression)
#endif

/* GCC and CAR versions */
#define ASSERT(x) {							\
	if (!__build_time_assert(x) && !(x)) {				\
		printk(BIOS_EMERG,					\
			"ASSERTION ERROR: file '%s', line %d\n",	\
			__ASSERT_FILE__, __ASSERT_LINE__);		\
		MOCK_ASSERT(!!(x), #x);					\
		if (CONFIG(FATAL_ASSERTS))				\
			hlt();						\
	}								\
}
#define ASSERT_MSG(x, msg) {						\
	if (!__build_time_assert(x) && !(x)) {				\
		printk(BIOS_EMERG,					\
			"ASSERTION ERROR: file '%s', line %d\n",	\
			__ASSERT_FILE__, __ASSERT_LINE__);		\
		printk(BIOS_EMERG, "%s", msg);				\
		MOCK_ASSERT(!!(x), (msg));				\
		if (CONFIG(FATAL_ASSERTS))				\
			hlt();						\
	}								\
}
#define BUG() {								\
	printk(BIOS_EMERG,						\
		"ERROR: BUG ENCOUNTERED at file '%s', line %d\n",	\
		__ASSERT_FILE__, __ASSERT_LINE__);			\
	MOCK_ASSERT(0, "BUG ENCOUNTERED");				\
	if (CONFIG(FATAL_ASSERTS))					\
		hlt();							\
}

#define assert(statement)	ASSERT(statement)

/*
 * These macros can be used to assert that a certain branch of code is dead and
 * will be compile-time eliminated. This differs from _Static_assert(), which
 * will generate a compiler error even if the scope it was called from is dead
 * code. This may be useful to double-check things like constants that are only
 * valid if a certain Kconfig option is set.
 *
 * The error message when this hits will look like this:
 *
 * ramstage/lib/bootmode.o: In function `display_init_required':
 * bootmode.c:42: undefined reference to `_dead_code_assertion_failed'
 */
extern void _dead_code_assertion_failed(void) __attribute__((noreturn));
#define dead_code() _dead_code_assertion_failed()

/* This can be used in the context of an expression of type 'type'. */
#define dead_code_t(type) ({ \
	dead_code(); \
	*(type *)(uintptr_t)0; \
})

#if ENV_X86_64
#define pointer_to_uint32_safe(x) ({ \
	if ((uintptr_t)(x) > 0xffffffffUL) \
		die("Cast from pointer to uint32_t overflows"); \
	(uint32_t)(uintptr_t)(x); \
})
#else
#define pointer_to_uint32_safe(x) ({ \
	(uint32_t)(uintptr_t)(x); \
})
#endif
#endif // __ASSERT_H__