/*
 * Copyright (C) 2020-2021 Aloysius Indrayanto
 *
 * This file is part of the eCxx library, see LICENSE file for the license details.
 */

/*
 * ----------------------------------------------------------------------------------
 * The code in this file is copied (with a slight modifications) from:
 *     StackThunk.h - allow use second stack for subsystem that needs a significant
 *                    amount of stack space
 * ----------------------------------------------------------------------------------
 * Copyright (C) 2017 Earle F. Philhower, III. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free Software
 * Foundation; either version 2.1 of the License, or (at your option) any later
 * version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along
 * with this library; if not, write to the Free Software Foundation, Inc., 51
 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 * ----------------------------------------------------------------------------------
 */


#include <eCxx/eCxx_Internal.h>


#ifndef ECXX_PLATFORM_NODEMCU_STACK_THUNK_H
#define ECXX_PLATFORM_NODEMCU_STACK_THUNK_H


extern "C" {


//
// Global variables required for thunking operation
//
extern eCxx::uint32_t* stack_thunk_ptr;
extern eCxx::uint32_t* stack_thunk_top;
extern eCxx::uint32_t* stack_thunk_save;
extern eCxx::uint32_t  stack_thunk_refcnt;


//
// Function prototypes
//
extern void stack_thunk_add_ref();
extern void stack_thunk_del_ref();
extern void stack_thunk_repaint();

extern eCxx::uint32_t stack_thunk_get_refcnt();
extern eCxx::uint32_t stack_thunk_get_stack_top();
extern eCxx::uint32_t stack_thunk_get_stack_bot();
extern eCxx::uint32_t stack_thunk_get_cont_sp();
extern eCxx::uint32_t stack_thunk_get_max_usage();

extern void stack_thunk_dump_stack();
extern void stack_thunk_fatal_overflow();


//
// Thunking macro
//
#if __GNUC__ < 8
    #define VOLATILE __volatile__
#else
    #define VOLATILE
#endif

#define make_stack_thunk(fcnToThunk)                                                                   \
    __asm__ VOLATILE ( "                                                                           \n\t\
        .text                                                                                      \n\t\
        .literal_position                                                                          \n\t\
        .literal .LC_STACK_VALUE"#fcnToThunk", 0xDEADBEEF                                          \n\t\
                                                                                                   \n\t\
        .text                                                                                      \n\t\
        .align 4                                                                                   \n\t\
        .global thunk_"#fcnToThunk"                                                                \n\t\
        .type thunk_"#fcnToThunk", @function                                                       \n\t\
                                                                                                   \n\t\
        thunk_"#fcnToThunk":                                                                       \n\t\
        addi   a1,  a1,  -16                     /* Allocate space for saved registers on stack */ \n\t\
        s32i   a0,  a1,  12                      /* Store a0, trounced by calls                 */ \n\t\
        s32i   a15, a1,  8                       /* Store a15 (our temporary one)               */ \n\t\
        movi   a15, stack_thunk_save             /* Store a1(sp) in temp space                  */ \n\t\
        s32i   a1,  a15, 0                                                                         \n\t\
        movi   a15, stack_thunk_top              /* Load a1(sp) with thunk stack                */ \n\t\
        l32i.n a1,  a15, 0                                                                         \n\t\
        call0  "#fcnToThunk"                     /* Do the call                                 */ \n\t\
                                                 /* Check the stack canary wasn't overwritten   */ \n\t\
        movi   a15, stack_thunk_ptr                                                                \n\t\
        l32i.n a15, a15, 0                       /* a15 now has the pointer to stack end        */ \n\t\
        l32i.n a15, a15, 0                       /* a15 now has contents of last stack entry    */ \n\t\
        l32r   a0,  .LC_STACK_VALUE"#fcnToThunk" /* a0 now has the check value                  */ \n\t\
        beq    a0,  a15, .L1"#fcnToThunk"                                                          \n\t\
        call0  stack_thunk_fatal_overflow                                                          \n\t\
        .L1"#fcnToThunk":                                                                          \n\t\
        movi   a15, stack_thunk_save             /* Restore a1(sp)                              */ \n\t\
        l32i.n a1,  a15, 0                                                                         \n\t\
        l32i.n a15, a1,  8                       /* Restore the saved registers                 */ \n\t\
        l32i.n a0,  a1,  12                                                                        \n\t\
        addi   a1,  a1,  16                      /* Free up stack and return to caller          */ \n\t\
        ret                                                                                        \n\t\
       .size thunk_"#fcnToThunk", . - thunk_"#fcnToThunk"                                          \n\t\
    " );


} // extern "C"


#endif
