RL78 FreeRTOS APIを特別なおまじない記述無しで割り込みルーチンから呼び出せるようにしてみた(CC-RL/GNURL78)

こんにちは。NoMaYです。

別スレッド『Amazon AWSのFreeRTOS Kernel Developer GuideのサンプルコードをRenesas RX SimulatorのDebug Consoleで試せるようにしてみた』で気付いたことですが、FreeRTOSでは、ポートレイヤーの実装に内蔵周辺のソフトウェア割り込み機能(これはCPUのソフトウェア割り込み命令のことでは無いです)を使用することで、素朴に簡単に割り込みルーチンからFreeRTOS APIを呼び出せるようになることに気付きました。そこで、FreeRTOS v10.2.1 RL78ポートレイヤーをそのように改変してみました。

以下、プロジェクトのファイル一式です。(CC-RL版はCS+ V8.01/e2 stuiod v7.40+CC-RL V1.02でビルド(e2 studio用.project/.cproject等を同梱))(GNURL78版はe2 studio v7.40+GNURL78 2019q2(4.9.2.201902)でビルド)(共にzipファイルをe2 studioに直接インポート可能) プロジェクト構造は極力RX版と同じにしてあります。([追記] すみません。以下に含まれるMAPファイルが別のものでした。タスク側のクリティカルセクションが以前のままのものでした。この投稿の末尾に追加したMOTファイルのzipファイルには訂正版を入れてあります。)

sim_rl78_freertos_full_demo_ccrl_c_csplus_20190705.zip    628KB
sim_rl78_freertos_full_demo_gnurl78_c_e2v740_20190705.zip    582KB

そのスレッドに投稿していたRL78ポートレイヤー(これもFreeRTOS v10.2.1 RL78ポートレイヤーを改変したものです)では、割り込みルーチンからFreeRTOS APIを呼び出すには以下のような特別なおまじないを記述する必要がありました。(以下はCC-RLの場合のものですがGNURL78でも同様です。)

#pragma interrupt r_intc3_interrupt(vect=INTP3)
/* Start user code for pragma. Do not edit comment generated here */
R_PRAGMA_FREERTOS_INTERRUPT(r_intc3_interrupt)
#define r_intc3_interrupt _r_intc3_interrupt
/* End user code. Do not edit comment generated here */

そのスレッドにはRXポートレイヤーの改変についても投稿していたのですが、その時、最初に書いたことに気付きました。RL78の内蔵周辺には専用のソフトウェア割り込み機能はありませんが、以下のハードウェアマニュアルに記載されている通り、プログラムで割り込み要求フラグをセットすると割り込みを発生させることが出来ますので、実質的に任意の空き割り込みをソフトウェア割り込み機能として使うことが出来ます。(今回は、とりあえず、ウォッチドッグタイマのオーバフロー時間の75%到達のインターバル割り込みを使用してみました。)

RL78/G13 ユーザーズマニュアル ハードウェア編からの抜粋
www.renesas.com/ja-jp/doc/products/mpumcu/doc/rl78/r01uh0146jj0340_rl78g13.pdf



なお、今回のRL78ポートレイヤーの改変で、先ほどの特別なおまじないを記述する必要は無くなりますが、割り込みルーチンからFreeRTOS APIを呼び出してブロック解除待ちタスクをブロック解除する場合のタスク切り替えの遅延時間が以下のRenesas RL78 SimulatorのSimulator GUIの画面コピーのように数十クロックほど(32MHz動作では600nsほど)延びます。(以下はCC-RLの場合のものですがGNURL78でも同様(ただし数百nsほど長い)です。) ちなみに、タスク側のクリティカルセクション(その区間では割り込み禁止となる)への出入りによるタイミングへの影響を避ける為、今回はクリティカルセクションとして扱うことしていませんので、以下の画面コピーでは別スレッドの画面コピーよりもポート出力トグルの速さが速くなっています。

今回の改変前:


今回の改変後:


以下、今回のRL78ポートレイヤの改変内容です。コメントの訂正や不要になったルーチンの削除は省略しています。(以下はCC-RLの場合のものですがGNURL78でも同様です。)

src/FreeRTOS/Source/portable/Renesas/RL78/portmacro.h
今回の改変前:

/* Task utilities. */
#define portYIELD() __brk()
#define portYIELD_FROM_ISR( xHigherPriorityTaskWoken ) if( xHigherPriorityTaskWoken ) vTaskSwitchContext()
#define portNOP()   __nop()

今回の改変後: 赤文字行を追加/変更

#include "iodefine.h"
/* Task utilities. */
#define portYIELD() do{ WDTIIF = 1; } while (0)
#define portYIELD_FROM_ISR( xHigherPriorityTaskWoken )  if( xHigherPriorityTaskWoken ) portYIELD()
#define portNOP()   __nop()

src/FreeRTOS/Source/portable/Renesas/RL78/port.c
今回の改変後: 赤文字行を追加

BaseType_t xPortStartScheduler( void )
{
    /* Setup the hardware to generate the tick.  Interrupts are disabled when
    this function is called. */
    configSETUP_TICK_INTERRUPT();

    /* Setup the hardware to generate the yield interrupt. */
    WDTIPR1 = 1;
    WDTIPR0 = 1;
    WDTIMK = 0;

    /* Restore the context of the first task that is going to run. */
    vPortStartFirstTask();

    /* Execution should not reach here as the tasks are now running! */
    return pdTRUE;
}

src/FreeRTOS/Source/portable/Renesas/RL78/portasm.asm
今回の改変前:

_vPortYield:
    portSAVE_CONTEXT                ; Save the context of the current task.
    call@      _vTaskSwitchContext  ; Call the scheduler to select the next task.
    portRESTORE_CONTEXT             ; Restore the context of the next task to run.
    retb
    _vPortYield      .VECTOR    0x7E

今回の改変後: 赤文字行を変更

_vPortYield:
    portSAVE_CONTEXT                ; Save the context of the current task.
    call@      _vTaskSwitchContext  ; Call the scheduler to select the next task.
    portRESTORE_CONTEXT             ; Restore the context of the next task to run.
    reti
    _vPortYield      .VECTOR    0x04

以下、今回のタスク側のプログラムです。先ほど書いた通り、今回は各タスクのポート出力をトグルさせる部分をクリティカルセクションとして扱うことしていません。(なお、src/frtos_config/FreeRTOSConfig.hの#define FREERTOS_USER_MAIN 0の行で0→1の変更を行うことで以下が実行されるようになります。)

src/user_main.c

void main_task(void *pvParameters)
{
    (void) pvParameters;

    while (1)
    {
        P1_bit.no6 = !P1_bit.no6;
    }
}

void second_task(void *pvParameters)
{
    (void) pvParameters;

    while (1)
    {
        P1_bit.no5 = !P1_bit.no5;
    }
}

void third_task(void *pvParameters)
{
    (void) pvParameters;

    while (1)
    {
        P1_bit.no4 = !P1_bit.no4;
    }
}

void intp3_task(void *pvParameters)
{
    (void) pvParameters;

    R_INTC3_Start();

    LED_INIT();
    while (1)
    {
        xSemaphoreTake( xSemaphoreINTP3, portMAX_DELAY );
        LED_BIT = !LED_BIT;
    }
}


[追記]

MOTファイル

sim_rl78_freertos_full_demo_ccrl_c_csplus_20190705_mot.zip    2019/07/12追加
sim_rl78_freertos_full_demo_gnurl78_c_e2v740_20190705_mot.zip    2019/07/24追加

Parents
  • こんにちは。NoMaYです。

    Call Walker (CC-RL+CS+)で見積もったスタック使用量の妥当性を検証してみました。なお、そもそも(RTOS未使用時においても)Call Walker (CC-RL+CS+)によるスタック使用量の見積もりは適切ではないですよね。あまりに当たり前過ぎて、皆さん不問に付しているのだとは思うのですが、(少なくとも多重割り込みを使用していない場合において、)必要スタックサイズの値は以下ではないかと思うのですが、その点が考慮されてませんよね、、、

    本来の必要スタックサイズの見積もり(多重割り込みを使用していない場合):

    必要サイズ = Cスタートアップルーチンから数えて通常処理で最も深くなるサイズ + 割り込み処理の内で最も深くなるサイズ

    Call Walker (CC-RL+CS+)での必要スタックサイズの見積もり:

    必要サイズ = 通常処理も割り込み処理も区別することなく、単に最も深くなるサイズ

    ですので、私も、そうした点は手計算するものとして検証しました。ですが、検証といっても、大したことをする訳でもなく、デバッガ上で、ブレークポイントを設定してブレークさせたり、ステップ実行させたりして、最も深くなる位置でのSP(スタックポインタ)の値をウォッチウィンドウで確認してみただけです。また、しらみつぶしに確認した訳ではなく、事例を絞って確認しました。あと、ルネサスRL78シミュレータで確認しました。

    確認結果:

    (A) 見積もりと実動作は概ね一致

    ただし

    (B) 関数/サブルーチンの最後のCALL命令をBR命令に置き換える最適化が行われた箇所(および、CALL命令をBR命令で置き換えた後の分岐先が連続していたのでBR命令さえ削除された箇所)の影響により実動作の方がサイズが小さかった(SPの値としては大きかった)という不一致あり(実害は無い)

    また

    (C) 所望の実行経路を今回のサンプルプログラムですぐに実現することが出来なかったので断念したものあり

    さらに

    (D) スタック渡し引数で使用されるスタックが、呼び出し元の関数に計上されるのか、呼び出し先の関数に計上されるのか、調べてみたところ、(少なくともCC-RL V1.02では)呼び出し元の関数に計上されていたので、このことは見積もりの方がサイズが大きくなる方向に作用すると思います

    確認事例:

    (1) main_task(レジスタ渡し引数有り+戻り番地+自動変数無し、関数/サブルーチン呼び出し無し)での確認 ⇒ 一致
    (2) r_intc0_interrupt(割り込み処理)から数えて最も深くなる場所での確認 ⇒ 一致
    (3) _start(Cスタートアップルーチンのアセンブラ)から数えて通常処理で最も深くなる場所での確認 ⇒ 不一致(実害無し)
    (4) _vPortYield(FreeRTOSポートレイヤのアセンブラの割り込み処理の1つ)から数えて最も深くなる場所での確認 ⇒ 一致
    (5) _vPortTickISR(FreeRTOSポートレイヤのアセンブラの割り込み処理の1つ)から数えて最も深くなる場所での確認 ⇒ 断念
    (6) スタック渡し引数がある関数を記述してmain_taskから呼び出してみた時の例

    また、今回、確認がやり易くなるかと考えて、各タスクの優先順位を以下の通りに変更してみました。(実質、main_task実行中にr_intc0_interruptが実行されるケースが多くなる、ようにしてみました。)

    src/frtos_startup/freertos_object_init.c (赤文字箇所を変更)

    void Kernel_Object_init (void)
    {
        ...略...

        xTaskCreateStatic_R_Helper( main_task, "MAIN_TASK", pdBYTES_TO_STACK_DEPTH(1024), NULL, 1, NULL );

        xTaskCreateStatic_R_Helper( task_LED0, "task_LED0", pdBYTES_TO_STACK_DEPTH(1024), NULL, 0/*2*/, NULL );

        xTaskCreateStatic_R_Helper( task_LED1, "task_LED1", pdBYTES_TO_STACK_DEPTH(1024), NULL, 2, NULL );

        xTaskCreateStatic_R_Helper( task_CONIO, "task_CONIO", pdBYTES_TO_STACK_DEPTH(1024), NULL, 0/*3*/, NULL);

        ...略...

    } /* End of function Kernel_Object_init()*/

    ちなみに、mapファイルで調べたmain_taskのスタック領域は 0x0f3f5a ~ 0x0f435a となっていました。あと、_startのスタック領域は 0x0f510e ~ 0x0ffe20 となっていました。

    DefaultBuild/freertos_sampleprog2_ccrl_c.map

    FILE=DefaultBuild\freertos_object_init.obj
                                      000f3f5a  000f5011      10b8
      _main_task_xStackBuffer@1@Kernel_Object_init
                                      000f3f5a       400   data ,l         1
      _main_task_xTCBBuffer@2@Kernel_Object_init
                                      000f435a        2e   data ,l         1
    Absolute value symbols
    FILE=rlink_generates_05
    ...
      __STACK_ADDR_START
                                      000ffe20         0   none ,g         1
      __STACK_ADDR_END
                                      000f510e         0   none ,g         1

    以下、それぞれの場合のCS+のルネサスRL78シミュレータでの実行結果の画面コピーです。なお、今回、Call Walkerの表示設定は以下の通りに変更しています。あと、CS+のビルドツールのプロパティを変更して、アセンブラソースを出力させるようにしています。

    [View]→[Show Used Stack]をチェック
    [View]→[Show All Symbols]をチェック


    [アセンブリソースファイルにコメントを出力する]を[いいえ]に設定(デフォルト設定) (画面コピーの大きさを少し小さくする為)
    [アセンブリソースファイルを出力する]を[はい(-asm_path)]に設定


    (1) main_task(レジスタ渡し引数有り+戻り番地+自動変数無し、関数/サブルーチン呼び出し無し)での確認

    SP期待値: 0x0f4356 (0x0f435a - 4)
    SP確認値: 0x0f4356 ⇒ OK(一致)



    (2) r_intc0_interrupt(割り込み処理)から数えて最も深くなる場所での確認

    SP期待値: 0x0f432e (0x0f435a - 4 - 40) : 4 = main_taskでの使用量、40 = r_intc0_interruptでの使用量
    SP確認値: 0x0f432e ⇒ OK(一致)



    (3) _start(Cスタートアップルーチンのアセンブラ)から数えて通常処理で最も深くなる場所での確認

    SP期待値: 0x0ffe10 (0x0ffe20 - 16)
    SP確認値: 0x0ffe18 ⇒ △(不一致だが実害無し)



    (4) _vPortYield(FreeRTOSポートレイヤのアセンブラの割り込み処理の1つ)から数えて最も深くなる場所での確認

    SP期待値: 0x0f4344 (0x0f435a - 4 - 22) : 4 = main_taskでの使用量、22 = _vPortYield割り込みでの使用量
    SP確認値: 0x0f4340 ⇒ OK(一致)



    (5) _vPortTickISR(FreeRTOSポートレイヤのアセンブラの割り込み処理の1つ)から数えて最も深くなる場所での確認

    SP期待値: 0x0f4330 (0x0f435a - 4 - 38) : 4 = main_taskでの使用量、38 = _vPortTickISR割り込みでの使用量
    SP確認値: Unknown(所望の実行経路を今回のサンプルプログラムですぐに実現することが出来なかったので断念)



    (6) スタック渡し引数がある関数を記述してmain_taskから呼び出してみた時の例

    スタック渡し引数で使用されるスタックは呼び出し元の関数のスタックサイズに計上されていました。このことは見積もり値の方が大きくなる方向に作用すると思います。

    main_task.c

    uint16_t foo3(uint16_t a, uint16_t b, uint16_t c);
    uint16_t foo4(uint16_t a, uint16_t b, uint16_t c, uint16_t d);

    uint16_t foo3(uint16_t a, uint16_t b, uint16_t c)
    {
        return a + b + c;
    }

    uint16_t foo4(uint16_t a, uint16_t b, uint16_t c, uint16_t d)
    {
        return a + b + c + d;
    }

    uint16_t a, b, c, d;
    volatile uint16_t r;

    void main_task(void *pvParameters)
    {
        INTERNAL_NOT_USED(pvParameters);
        while(1)
        {
           r = foo3( a, b, c );
           r = foo4( a, b, c, d );
        }
    }

    main_task.asm

    _foo3:
        .STACK _foo3 = 4
        addw ax, bc
        addw ax, de
        ret

    _foo4:
        .STACK _foo4 = 4
        movw hl, ax
        movw ax, [sp+0x04]
        addw ax, de
        movw de, ax
        movw ax, hl
        addw ax, bc
        addw ax, de
        ret

    _main_task:
        .STACK _main_task = 6
    .BB@LABEL@3_1:  ; bb11
        movw de, !LOWW(_c)
        movw bc, !LOWW(_b)
        movw ax, !LOWW(_a)
        call $!_foo3 ⇒ ここから先の呼び出しが深くなった時にスタック使用量に +2 のゲタが発生する
        movw !LOWW(_r), ax
        movw ax, !LOWW(_d)
        push ax
        movw de, !LOWW(_c)
        movw bc, !LOWW(_b)
        movw ax, !LOWW(_a)
        call $!_foo4 ⇒ ここから先の呼び出しではそのようなゲタは発生しない
        pop hl
        movw !LOWW(_r), ax
        br $.BB@LABEL@3_1

     

Reply
  • こんにちは。NoMaYです。

    Call Walker (CC-RL+CS+)で見積もったスタック使用量の妥当性を検証してみました。なお、そもそも(RTOS未使用時においても)Call Walker (CC-RL+CS+)によるスタック使用量の見積もりは適切ではないですよね。あまりに当たり前過ぎて、皆さん不問に付しているのだとは思うのですが、(少なくとも多重割り込みを使用していない場合において、)必要スタックサイズの値は以下ではないかと思うのですが、その点が考慮されてませんよね、、、

    本来の必要スタックサイズの見積もり(多重割り込みを使用していない場合):

    必要サイズ = Cスタートアップルーチンから数えて通常処理で最も深くなるサイズ + 割り込み処理の内で最も深くなるサイズ

    Call Walker (CC-RL+CS+)での必要スタックサイズの見積もり:

    必要サイズ = 通常処理も割り込み処理も区別することなく、単に最も深くなるサイズ

    ですので、私も、そうした点は手計算するものとして検証しました。ですが、検証といっても、大したことをする訳でもなく、デバッガ上で、ブレークポイントを設定してブレークさせたり、ステップ実行させたりして、最も深くなる位置でのSP(スタックポインタ)の値をウォッチウィンドウで確認してみただけです。また、しらみつぶしに確認した訳ではなく、事例を絞って確認しました。あと、ルネサスRL78シミュレータで確認しました。

    確認結果:

    (A) 見積もりと実動作は概ね一致

    ただし

    (B) 関数/サブルーチンの最後のCALL命令をBR命令に置き換える最適化が行われた箇所(および、CALL命令をBR命令で置き換えた後の分岐先が連続していたのでBR命令さえ削除された箇所)の影響により実動作の方がサイズが小さかった(SPの値としては大きかった)という不一致あり(実害は無い)

    また

    (C) 所望の実行経路を今回のサンプルプログラムですぐに実現することが出来なかったので断念したものあり

    さらに

    (D) スタック渡し引数で使用されるスタックが、呼び出し元の関数に計上されるのか、呼び出し先の関数に計上されるのか、調べてみたところ、(少なくともCC-RL V1.02では)呼び出し元の関数に計上されていたので、このことは見積もりの方がサイズが大きくなる方向に作用すると思います

    確認事例:

    (1) main_task(レジスタ渡し引数有り+戻り番地+自動変数無し、関数/サブルーチン呼び出し無し)での確認 ⇒ 一致
    (2) r_intc0_interrupt(割り込み処理)から数えて最も深くなる場所での確認 ⇒ 一致
    (3) _start(Cスタートアップルーチンのアセンブラ)から数えて通常処理で最も深くなる場所での確認 ⇒ 不一致(実害無し)
    (4) _vPortYield(FreeRTOSポートレイヤのアセンブラの割り込み処理の1つ)から数えて最も深くなる場所での確認 ⇒ 一致
    (5) _vPortTickISR(FreeRTOSポートレイヤのアセンブラの割り込み処理の1つ)から数えて最も深くなる場所での確認 ⇒ 断念
    (6) スタック渡し引数がある関数を記述してmain_taskから呼び出してみた時の例

    また、今回、確認がやり易くなるかと考えて、各タスクの優先順位を以下の通りに変更してみました。(実質、main_task実行中にr_intc0_interruptが実行されるケースが多くなる、ようにしてみました。)

    src/frtos_startup/freertos_object_init.c (赤文字箇所を変更)

    void Kernel_Object_init (void)
    {
        ...略...

        xTaskCreateStatic_R_Helper( main_task, "MAIN_TASK", pdBYTES_TO_STACK_DEPTH(1024), NULL, 1, NULL );

        xTaskCreateStatic_R_Helper( task_LED0, "task_LED0", pdBYTES_TO_STACK_DEPTH(1024), NULL, 0/*2*/, NULL );

        xTaskCreateStatic_R_Helper( task_LED1, "task_LED1", pdBYTES_TO_STACK_DEPTH(1024), NULL, 2, NULL );

        xTaskCreateStatic_R_Helper( task_CONIO, "task_CONIO", pdBYTES_TO_STACK_DEPTH(1024), NULL, 0/*3*/, NULL);

        ...略...

    } /* End of function Kernel_Object_init()*/

    ちなみに、mapファイルで調べたmain_taskのスタック領域は 0x0f3f5a ~ 0x0f435a となっていました。あと、_startのスタック領域は 0x0f510e ~ 0x0ffe20 となっていました。

    DefaultBuild/freertos_sampleprog2_ccrl_c.map

    FILE=DefaultBuild\freertos_object_init.obj
                                      000f3f5a  000f5011      10b8
      _main_task_xStackBuffer@1@Kernel_Object_init
                                      000f3f5a       400   data ,l         1
      _main_task_xTCBBuffer@2@Kernel_Object_init
                                      000f435a        2e   data ,l         1
    Absolute value symbols
    FILE=rlink_generates_05
    ...
      __STACK_ADDR_START
                                      000ffe20         0   none ,g         1
      __STACK_ADDR_END
                                      000f510e         0   none ,g         1

    以下、それぞれの場合のCS+のルネサスRL78シミュレータでの実行結果の画面コピーです。なお、今回、Call Walkerの表示設定は以下の通りに変更しています。あと、CS+のビルドツールのプロパティを変更して、アセンブラソースを出力させるようにしています。

    [View]→[Show Used Stack]をチェック
    [View]→[Show All Symbols]をチェック


    [アセンブリソースファイルにコメントを出力する]を[いいえ]に設定(デフォルト設定) (画面コピーの大きさを少し小さくする為)
    [アセンブリソースファイルを出力する]を[はい(-asm_path)]に設定


    (1) main_task(レジスタ渡し引数有り+戻り番地+自動変数無し、関数/サブルーチン呼び出し無し)での確認

    SP期待値: 0x0f4356 (0x0f435a - 4)
    SP確認値: 0x0f4356 ⇒ OK(一致)



    (2) r_intc0_interrupt(割り込み処理)から数えて最も深くなる場所での確認

    SP期待値: 0x0f432e (0x0f435a - 4 - 40) : 4 = main_taskでの使用量、40 = r_intc0_interruptでの使用量
    SP確認値: 0x0f432e ⇒ OK(一致)



    (3) _start(Cスタートアップルーチンのアセンブラ)から数えて通常処理で最も深くなる場所での確認

    SP期待値: 0x0ffe10 (0x0ffe20 - 16)
    SP確認値: 0x0ffe18 ⇒ △(不一致だが実害無し)



    (4) _vPortYield(FreeRTOSポートレイヤのアセンブラの割り込み処理の1つ)から数えて最も深くなる場所での確認

    SP期待値: 0x0f4344 (0x0f435a - 4 - 22) : 4 = main_taskでの使用量、22 = _vPortYield割り込みでの使用量
    SP確認値: 0x0f4340 ⇒ OK(一致)



    (5) _vPortTickISR(FreeRTOSポートレイヤのアセンブラの割り込み処理の1つ)から数えて最も深くなる場所での確認

    SP期待値: 0x0f4330 (0x0f435a - 4 - 38) : 4 = main_taskでの使用量、38 = _vPortTickISR割り込みでの使用量
    SP確認値: Unknown(所望の実行経路を今回のサンプルプログラムですぐに実現することが出来なかったので断念)



    (6) スタック渡し引数がある関数を記述してmain_taskから呼び出してみた時の例

    スタック渡し引数で使用されるスタックは呼び出し元の関数のスタックサイズに計上されていました。このことは見積もり値の方が大きくなる方向に作用すると思います。

    main_task.c

    uint16_t foo3(uint16_t a, uint16_t b, uint16_t c);
    uint16_t foo4(uint16_t a, uint16_t b, uint16_t c, uint16_t d);

    uint16_t foo3(uint16_t a, uint16_t b, uint16_t c)
    {
        return a + b + c;
    }

    uint16_t foo4(uint16_t a, uint16_t b, uint16_t c, uint16_t d)
    {
        return a + b + c + d;
    }

    uint16_t a, b, c, d;
    volatile uint16_t r;

    void main_task(void *pvParameters)
    {
        INTERNAL_NOT_USED(pvParameters);
        while(1)
        {
           r = foo3( a, b, c );
           r = foo4( a, b, c, d );
        }
    }

    main_task.asm

    _foo3:
        .STACK _foo3 = 4
        addw ax, bc
        addw ax, de
        ret

    _foo4:
        .STACK _foo4 = 4
        movw hl, ax
        movw ax, [sp+0x04]
        addw ax, de
        movw de, ax
        movw ax, hl
        addw ax, bc
        addw ax, de
        ret

    _main_task:
        .STACK _main_task = 6
    .BB@LABEL@3_1:  ; bb11
        movw de, !LOWW(_c)
        movw bc, !LOWW(_b)
        movw ax, !LOWW(_a)
        call $!_foo3 ⇒ ここから先の呼び出しが深くなった時にスタック使用量に +2 のゲタが発生する
        movw !LOWW(_r), ax
        movw ax, !LOWW(_d)
        push ax
        movw de, !LOWW(_c)
        movw bc, !LOWW(_b)
        movw ax, !LOWW(_a)
        call $!_foo4 ⇒ ここから先の呼び出しではそのようなゲタは発生しない
        pop hl
        movw !LOWW(_r), ax
        br $.BB@LABEL@3_1

     

Children
No Data