U0 Exit()
{//Terminate own task.
        if (Fs == sys_focus_task && IsDebugMode)
        {
                LBts(&Fs->task_flags, TASKf_KILL_AFTER_DEBUG);
                G;
        }
        else
        {
                if (!Gs->num && !IsDebugMode)
                        SingleUser(OFF);

                Fs->rflags      = RFlagsGet;
                Fs->rsp         = RSPGet;
                Fs->rbp         = RBPGet;
                Fs->rip         = $;
                CLI
                LBts(&Fs->task_flags, TASKf_KILL_TASK);
                TaskEndNow;
        }
}

Bool TaskValidate(CTask *task)
{//return TRUE if task looks valid.
        if (!(0 < task <= I32_MAX) || task->addr != task || task->task_signature != TASK_SIGNATURE_VAL)
                return FALSE;
        else
                return TRUE;
}

I64 BirthWait(CTask **_task, I64 task_num=-1)
{//Wait for task valid and not task_num.
        while (!TaskValidate(*_task) || (*_task)->task_num == task_num)
                Yield;

        return (*_task)->task_num;
}

U0 DeathWait(CTask **_task, Bool send_exit=FALSE)
{//Wait for task death.
        if (send_exit && TaskValidate(*_task))
        {
                TaskWait(*_task, TRUE);
                XTalk(*_task, "Exit;\n");
        }
        while (TaskValidate(*_task))
                Yield;
}

Bool Kill(CTask *task, Bool wait=TRUE, Bool just_break=FALSE)
{//Terminate other task.
        I64 i;

        if (TaskValidate(task))
        {
                if (just_break)
                {
                        if (task != Fs)
                                Break;
                        else
                        {//TODO wait
                                sys_focus_task = task;
                                LBts(sys_ctrl_alt_flags, CTRL_ALT_C);
                                return TRUE;
                        }
                }
                else
                {
                        if (task != sys_winmgr_task)
                        {
                                for (i = 0; i < mp_count; i++)
                                        if (task == cpu_structs[i].executive_task)
                                                return FALSE;
                                LBts(&task->task_flags, TASKf_KILL_TASK);
                                if (wait)
                                {
                                        do Yield;
                                        while (TaskValidate(task) && Bt(&task->task_flags, TASKf_KILL_TASK));
                                }
                                return TRUE;
                        }
                }
        }

        return FALSE;
}

Bool Suspend(CTask *task=NULL, Bool state=TRUE)
{//Tell scheduler to skip task.
        Bool res;

        if (!task)
                task = Fs;

        PUSHFD
        CLI
        if (TaskValidate(task))
                res = LBEqual(&task->task_flags, TASKf_SUSPENDED, state);
        else
                res = FALSE;
        POPFD

        return res;
}

Bool IsSuspended(CTask *task=NULL)
{//You might use this in a DrawIt() or Animatetask().
        if (!task)
                task = Fs;

        if (TaskValidate(task))
                return Bt(&task->task_flags, TASKf_SUSPENDED);
        else
                return FALSE;
}

CTaskStack *TaskStackNew(I64 stack_size, CTask *task)
{
        CTaskStack *tmps = MAlloc(stack_size + offset(CTaskStack.stack_base), task);

        tmps->next_stack        = NULL;
        tmps->stack_ptr         = NULL;
        tmps->stack_size        = MSize(tmps) - offset(CTaskStack.stack_base);

        return tmps;
}

#exe {Option(OPTf_NO_REG_VAR, ON);};
argpop I64 CallStackGrow(I64 stack_size_threshold, I64 stack_size, /*argpop*/I64 (*fp_addr)(...), ...)
{//Grow stack in call with any fixed num of args.
//See ::/Demo/StackGrow.CC.
        CTaskStack      *tmps, *tmps2, **_stack;
        I64                      res, *rsp, *rsp2, *old_stack;

        if (UnusedStack >= stack_size_threshold)
        {

                asm {
                        LEAVE
                        POP                     RAX     //return addr
                        ADD                     RSP, 16 //pop threshold, stack_size
                        POP                     RBX     // *f
                        ADD                     RSP, 8  //pop ARGC
                        PUSH                    RAX
                        JMP                     RBX     //CALL fp_addr()
                };

        }
        else
        {

                tmps2 = TaskStackNew(stack_size, Fs);
                tmps2->next_stack = tmps = Fs->stack;
                rsp2 = (&tmps2->stack_base)(U8 *) + tmps2->stack_size;
                old_stack = rsp = &argv[argc];
                while (argc-- > 0)
                        *--rsp2 = *--rsp;
                _stack = &Fs->stack;
                tmps->stack_ptr = rsp = RSPGet;
                asm {
                        IMPORT                  _FREE;  //We are in a function, not at global scope.
//The compiler treats these in isolation.

                        PUSHFD
                        POP                     RDX                     //flags
                        CLI
                        MOV                     RBX, U64 &tmps2[RBP]
                        MOV                     RAX, &_stack[RBP]
                        MOV                     U64 [RAX], RBX  //Fs->stack=tmps2
                        MOV                     RSP, U64 &rsp2[RBP]
                        PUSH                    RDX
                        POPFD

                        CALL                    U64 &fp_addr[RBP]
                        MOV                     U64 &res[RBP], RAX

                        PUSHFD
                        POP                     RDX                     //flags
                        CLI
                        MOV                     RBX, U64 &tmps[RBP]
                        MOV                     RAX, &_stack[RBP]
                        MOV                     U64 [RAX], RBX  //Fs->stack=tmps
                        MOV                     RSP, U64 &rsp[RBP]
                        PUSH                    RDX
                        POPFD

                        PUSH                    U64 &tmps2[RBP]
                        CALL                    _FREE

                        MOV                     RDX, U64 &old_stack[RBP]
                        MOV                     RBX, U64 8[RBP]
                        MOV                     RAX, U64 &res[RBP]
                        MOV                     RBP, U64 [RBP]
                        MOV                     RSP, RDX
                        JMP                     RBX                     //return
                };
        }

        return 0; //dummy to get rid of warning
}
;
#exe {Option(OPTf_NO_REG_VAR, OFF);};

I64 TaskInit(CTask *task, I64 stack_size)
{//Returns Fs of task
        CTaskStack *tmps;

        QueueInit(&task->code_heap->next_mem_blk);
        task->code_heap->last_mergable = NULL;
        if (task->code_heap != task->data_heap)
        {
                QueueInit(&task->data_heap->next_mem_blk);
                task->data_heap->last_mergable = NULL;
        }

        task->addr = task->next_task = task->last_task = task->next_input_filter_task = task->last_input_filter_task = task;

        task->task_num = sys_num_spawned_tasks++;

        task->rflags = RFLAGG_NORMAL;
        task->win_inhibit = WIG_TASK_DEFAULT;

        task->next_child_task = task->last_child_task = (&task->next_child_task)(U8 *) - offset(CTask.next_sibling_task);

        JobCtrlInit(&task->server_ctrl);
        QueueInit(&task->next_cc);
        QueueInit(&task->next_except);
        QueueInit(&task->next_ctrl);
        QueueInit(&task->next_ode);

        task->fpu_mmx = MAllocAligned(sizeof(CFPU), 0x10, task);
        MemCopy(task->fpu_mmx, SYS_FIXED_AREA + offset(CSysFixedArea.init_fpu_mmx), sizeof(CFPU));

        task->hash_table = HashTableNew(TASK_HASH_TABLE_SIZE, task);

        if (!stack_size)
                stack_size = MEM_DEFAULT_STACK;

        task->stack = tmps = TaskStackNew(stack_size, task);
        task->rsp = (&tmps->stack_base)(U8 *) + tmps->stack_size;

        task->text_attr                         = WHITE << 4 + BLUE;
        task->border_src                        = BDS_CONST;
        task->border_attr                       = DriveTextAttrGet(':');
        task->title_src                         = TTS_CONST;
        task->win_left                          = 1;
        task->win_right                         = text.cols - 2;
        task->win_top                           = text.rows / 5 + 3;
        task->win_bottom                        = text.rows - 2;

        if (blkdev.home_dir)
        {//Beware System TaskInit. I guess ok until DiskChange().
                task->cur_dv    = blkdev.let_to_drive[*blkdev.home_dir - 'A'];
                task->cur_dir   = StrNew(blkdev.home_dir + 2, task);
        }
        else
                task->cur_dir   = StrNew("/Home", task);

        Seed(, task);

        return task;
}

CTask *Spawn(U0 (*fp_start_addr)(U8 *data), U8 *data=NULL, U8 *task_name=NULL,
                                I64 target_cpu=-1,      // -1 for current CPU. See multi-core.
                                CTask *parent=NULL,     // NULL means system_task
                                I64 stack_size=0,       // 0=default
                                I64 flags=1 << JOBf_ADD_TO_QUE)
{//Create task on core running at address.
//Alloc CTask structure from code heap so addr will be short.
        //Could be alloced off of data heap.
        CTask *task;

        if (target_cpu >= 0)
                return SpawnQueue(fp_start_addr, data, task_name, target_cpu, parent, stack_size, flags);

        task = CAlloc(sizeof(CTask), sys_task->code_heap);
        task->task_signature = TASK_SIGNATURE_VAL;
        if (!task_name)
                task_name = "Unnamed Task";
        if (!parent)
                parent = Gs->executive_task;
        task->parent_task = parent;
        task->gs = parent->gs;
        if (sys_code_bp)
                task->code_heap = HeapCtrlInit(, task, sys_code_bp);
        if (sys_data_bp)
                task->data_heap = HeapCtrlInit(, task, sys_data_bp);
        else
                task->data_heap = task->code_heap;
        TaskInit(task, stack_size);
        task->rip = fp_start_addr;
        task->rsp(U8 *) -= 8;
        *task->rsp = data;
        task->rsp(U8 *) -= 8;
        *task->rsp = &Exit;
        task->hash_table->next = parent->hash_table;
        MemCopy(task->task_name, task_name, TASK_NAME_LEN);
        StrCopy(task->task_title, task->task_name);
        task->title_src = TTS_TASK_NAME;

        PUSHFD
        CLI
        if (Bt(&flags, JOBf_ADD_TO_QUE))
        {
                TaskQueueInsChild(task);
                TaskQueueIns(task);
        }
        POPFD

        return task;
}

U0 TaskDerivedValsUpdate(CTask *task=NULL, Bool update_z_buf=TRUE)
{//Those things calculated from other variables.
        if (!task)
                task = Fs;

        PUSHFD
        CLI
        while (LBts(&task->task_flags, TASKf_TASK_LOCK))
                PAUSE
        WinDerivedValsUpdate(task);
        if (fp_update_ctrls)
                (*fp_update_ctrls)(task);
        if (update_z_buf && Bt(&task->display_flags, DISPLAYf_SHOW))
                LBts(&sys_semas[SEMA_UPDATE_WIN_Z_BUF], 0);
        LBtr(&task->task_flags, TASKf_TASK_LOCK);
        POPFD
}

I64 ExeCmdLine(CCompCtrl *cc)
{//Terminal JIT-compile-and-execute loop for CCompCtrl.
        I64                      res = 0, type, old_title_src = Fs->title_src;
        U8                      *ptr, *ptr2, *ptr3, *machine_code, *old_task_title = StrNew(Fs->task_title);
        F64                      t0;
        CDocEntry       *doc_e;
        CDoc            *doc;

        if (Fs->title_src != TTS_LOCKED_CONST)
                Fs->title_src = TTS_CUR_LEX;
        while (cc->token && (cc->token != '}' || !(cc->flags & CCF_EXE_BLK)) )
        {
                if (Fs->title_src == TTS_CUR_LEX)
                {
                        ptr2 = &Fs->task_title;
                        ptr3 = ptr2 + STR_LEN - 1;
                        if (cc->lex_include_stack->flags & LFSF_DOC)
                        {
                                doc_e = cc->lex_include_stack->cur_entry;
                                doc   = cc->lex_include_stack->doc;
                                while (doc_e != doc && ptr2 < ptr3)
                                {
                                        switch (doc_e->type_u8)
                                        {
                                                case DOCT_TEXT:
                                                        ptr = doc_e->tag;
                                                        while (*ptr && ptr2 < ptr3)
                                                                *ptr2++ = *ptr++;
                                                        break;

                                                case DOCT_TAB:
                                                case DOCT_NEW_LINE:
                                                        *ptr2++ = '.';
                                                        break;
                                        }
                                        doc_e = doc_e->next;
                                }
                                if (ptr2 < ptr3)
                                        *ptr2 = 0;
                        }
                        else
                                if ((ptr = cc->lex_include_stack->line_start) && *ptr)
                                        MemCopy(ptr2, ptr, STR_LEN - 1);
                }
                cc->flags &= ~CCF_HAS_MISC_DATA;
                machine_code = LexStatement2Bin(cc, &type);
                if (machine_code != INVALID_PTR)
                {
                        if (!(cc->flags & CCF_JUST_LOAD))
                        {
                                t0 = tS;
                                res = Call(machine_code);
                                Fs->answer              = res;
                                Fs->answer_type = type;
                                Fs->answer_time = tS - t0;
                                Fs->new_answer  = TRUE;
                                cc->prompt_line = 0;
                        }
                        if (!(cc->flags & CCF_HAS_MISC_DATA))
                                Free(machine_code);
                }
        }
        if (Fs->title_src != TTS_LOCKED_CONST)
        {
                Fs->title_src = old_title_src;
                StrCopy(Fs->task_title, old_task_title);
        }
        Free(old_task_title);
        if (cc->flags & CCF_JUST_LOAD)
        {
                if (cc->error_count)
                        return FALSE;
                else
                        return TRUE;
        }
        else
                return res;
}

U0 ServerTaskCont()
{//Act as server task in a loop handling commands.
        I64 old_flags = RFlagsGet;

        FlushMessages;
        while (TRUE)
        {
                CLI
                if (JobsHandler(old_flags) && Fs->title_src == TTS_TASK_NAME)
                        MemCopy(Fs->task_title, Fs->task_name, TASK_NAME_LEN);
                FlushMessages;
                LBts(&Fs->task_flags, TASKf_IDLE);
                LBts(&Fs->task_flags, TASKf_AWAITING_MESSAGE);
                Yield;
                RFlagsSet(old_flags);
        }
}

U0 UserTaskCont()
{//Terminal key-input-execute loop.
        CCompCtrl       *cc;
        CDoc            *doc;
        Bool             cont = TRUE;

        do
        {
                cc = CompCtrlNew(, CCF_CMD_LINE | CCF_PROMPT | CCF_QUESTION_HELP);
                QueueInsert(cc, Fs->last_cc);
                try
                {
                        Lex(cc);
                        ExeCmdLine(cc);
                        cont = Bt(&cc->flags, CCf_PROMPT);
                        QueueRemove(cc);
                        CompCtrlDel(cc);
                }
                catch
                {
                        if ((doc = Fs->put_doc) && doc->doc_signature == DOC_SIGNATURE_VAL)
                                DocUnlock(doc);
                        PutExcept;
                }
        }
        while (cont);
}

U0 ServerCmdLine(I64 dummy=0)
{
        no_warn dummy;
        Fs->win_inhibit = WIG_USER_TASK_DEFAULT;
        CallExtStr("ServerStartUp");
        ServerTaskCont;
}

U0 UserCmdLine(I64 dummy=0)
{//A user task ends-up calling this.
        no_warn dummy;
        Fs->win_inhibit = WIG_USER_TASK_DEFAULT;
        CallExtStr("UserStartUp");
        if (!LBts(&Fs->display_flags, DISPLAYf_SHOW))
                Debug;
        UserTaskCont;
}

CTask *User(U8 *format=NULL, ...)
{//Create user term task.
        U8              *st;
        CTask   *task = Spawn(&UserCmdLine,, "Terminal");

        TaskWait(task);
        if (format)
        {
                st = StrPrintJoin(NULL, format, argc, argv);
                XTalk(task, st);
                Free(st);
        }

        return task;
}

U0 TaskDel(CTask *task)
{//We delay freeing in case lingering ptr to reincarnated.
        HeapCtrlDel(task->code_heap);
        if (task->data_heap != task->code_heap)
                HeapCtrlDel(task->data_heap);
        Free(task);
}

I64 TaskEnd()
{//Called with irqs off.
        CTask *task = Fs, *tmpt, *tmpt1;

        if (task == sys_task_being_screen_updated)
        {
                LBts(&task->task_flags, TASKf_KILL_TASK);
                return task->next_task;
        }
        if (task->task_end_cb)
        {
                task->wake_jiffy = 0;
                LBtr(&task->task_flags, TASKf_KILL_TASK);
                TaskResetAwaitingMessage(task);
                Suspend(task, FALSE);
                task->rip = task->task_end_cb;
                task->task_end_cb = NULL;
                return task;
        }
        if (task->parent_task && task->parent_task->popup_task == task)
        {
                task->parent_task->popup_task = NULL;
                Kill(task->parent_task, FALSE);
                return task->parent_task;
        }

        DrivesRelease;
        BlkDevsRelease;
        tmpt1 = (&task->next_child_task)(U8 *) - offset(CTask.next_sibling_task);
        tmpt = tmpt1->next_sibling_task;
        if (tmpt != tmpt1)
        {
                do
                {
                        LBts(&tmpt->task_flags, TASKf_KILL_TASK);
                        tmpt = tmpt->next_sibling_task;
                }
                while (tmpt != tmpt1);

                return task->next_task;
        }
        if (LBtr(&task->display_flags, DISPLAYf_SHOW))
                LBts(&sys_semas[SEMA_UPDATE_WIN_Z_BUF], 0);

        while (LBts(&task->task_flags, TASKf_TASK_LOCK))
                PAUSE
        while (LBts(&task->server_ctrl.flags, JOBCf_LOCKED))
                PAUSE

        JobQueueDel(&task->server_ctrl.next_waiting);
        JobQueueDel(&task->server_ctrl.next_done);

        if (IsRaw)
                LFBFlush;

        if (sys_focus_task == task)
        {
                if (!Gs->num)
                        SingleUser(OFF);
                sys_focus_task = NULL;
                if (fp_set_std_palette)
                        (*fp_set_std_palette)();
        }

        //QueueRemove
        task->task_signature(I64) = 0;

        tmpt  =task->next_input_filter_task;
        tmpt1 =task->last_input_filter_task;
        tmpt1->next_input_filter_task = tmpt;
        tmpt->last_input_filter_task  = tmpt1;

        tmpt  = task->next_sibling_task;
        tmpt1 = task->last_sibling_task;
        tmpt1->next_sibling_task = tmpt;
        tmpt->last_sibling_task  = tmpt1;

        tmpt = task->next_task; //save to return
        TaskQueueRemove(task);

        LBtr(&task->server_ctrl.flags, JOBCf_LOCKED);
        LBtr(&task->task_flags, TASKf_TASK_LOCK);

        task->wake_jiffy = counts.jiffies + DYING_JIFFIES;
        while (LBts(&Gs->cpu_flags, CPUf_DYING_TASK_QUE))
                PAUSE
        QueueInsert(task, Gs->last_dying);
        LBtr(&Gs->cpu_flags, CPUf_DYING_TASK_QUE);

        return tmpt;
}

U0 TaskKillDying()
{//Delay freeing to prevent asking for trouble with quick reincarnations.
//What if the user is doing this: DoTreeCheckers.
        CTaskDying *task, *task1;

        if (Gs->kill_jiffy < counts.jiffies)
        {//Avoid doing as many lock operations.
                while (LBts(&Gs->cpu_flags, CPUf_DYING_TASK_QUE))
                        PAUSE
                task = Gs->next_dying;
                while (task != &Gs->next_dying && task->wake_jiffy < counts.jiffies)
                {
                        task1 = task->next;
                        QueueRemove(task);
                        TaskDel(task);
                        task = task1;
                }
                LBtr(&Gs->cpu_flags, CPUf_DYING_TASK_QUE);
                Gs->kill_jiffy = counts.jiffies + DYING_JIFFIES;
        }
}