#![no_std]
#![no_main]
#![no_builtins]
#![feature(asm_experimental_arch)]
#![feature(fn_align)]
#![feature(asm_const)]
#![feature(naked_functions)]

use core::arch::asm;
use core::ffi::{c_char, c_void, CStr};
use core::panic::PanicInfo;

#[doc(hidden)]
pub const fn validate_cstr_contents(bytes: &[u8]) {
    let mut i = 0;
    while i < bytes.len() {
        if bytes[i] == b'\0' {
            panic!("null in string");
        }
        i += 1;
    }
}

macro_rules! cstr {
    ( $s:literal ) => {{
        $crate::validate_cstr_contents($s.as_bytes());
        unsafe { CStr::from_bytes_with_nul_unchecked(concat!($s, "\0").as_bytes()) }
    }};
}

const STR: &CStr = cstr!("Hello, World!");

extern "C" {
    fn strcpy(dst: *mut c_char, src: *const c_char) -> *mut c_char;
    static mut GSString: c_char;

    static __text_start: c_void;
    static __text_end: c_void;

    static __entry_start: c_void;
    static __entry_end: c_void;
}

#[inline(never)]
fn __main() {
    unsafe { strcpy(&mut GSString, STR.as_ptr()) };
}

#[rustfmt::skip] // rustfmt doesn't seem to like my beautifully justified comments.
#[no_mangle] // This can be removed, but then the function won't appear in the map.
#[link_section = ".entry"] // .entry will be located at 0x80205574 (see linker.ld).
#[repr(align(4))] // rustc emits an invalid instruction for alignment without this.
#[naked] // We jump to __main() from the asm block directly using j instead of jal,
// so __payload_entry() doesn't need an epilogue. rustc will output one by default,
// since it doesn't read the asm, so we use #[naked] to tell it not to. Previously,
// I tried setting the return type to -> !, which also works, but it's probably UB.
pub extern "C" fn __payload_entry() {
    unsafe {
        asm!(
            ".set noreorder          ",
            "   la     $v0, {lo}     ",
            "   la     $v1, {hi}     ",
            "1:                      ",
            "   cache 0x19, 0x00($v0)",
            "   cache 0x19, 0x10($v0)",
            "   cache 0x10, 0x00($v0)",
            "   sltu   $t0, $v0, $v1 ",
            "   bnez   $t0, 1b       ",
            "    addiu $v0, $v0, 0x20",
            "   j    {main}          ",
            "    nop                 ",
            ".set   reorder          ",
            lo   = sym __text_start,
            hi   = sym __text_end,
            main = sym __main,
            options(noreturn)
        )
    }
}

#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    loop {}
}
