From 944df9f5ce7ae5a6463144e5383f60cc5e5fcc45 Mon Sep 17 00:00:00 2001 From: Christian Vogelgsang Date: Sun, 13 Oct 2024 19:37:39 +0200 Subject: [PATCH] added VamosTask for Python code --- amitools/vamos/schedule/__init__.py | 2 +- amitools/vamos/schedule/scheduler.py | 30 +++- amitools/vamos/schedule/task.py | 83 ++++++++--- requirements-dev.txt | 1 + requirements-test.txt | 1 + test/unit/schedule_scheduler.py | 206 ++++++++++++++++++++++++++- test/unit/schedule_task.py | 27 +++- 7 files changed, 317 insertions(+), 33 deletions(-) diff --git a/amitools/vamos/schedule/__init__.py b/amitools/vamos/schedule/__init__.py index 18261ef0..d236f260 100644 --- a/amitools/vamos/schedule/__init__.py +++ b/amitools/vamos/schedule/__init__.py @@ -1,3 +1,3 @@ from .stack import Stack -from .task import NativeTask, TaskState +from .task import NativeTask, VamosTask, TaskState from .scheduler import Scheduler diff --git a/amitools/vamos/schedule/scheduler.py b/amitools/vamos/schedule/scheduler.py index ca457cb7..433b579d 100644 --- a/amitools/vamos/schedule/scheduler.py +++ b/amitools/vamos/schedule/scheduler.py @@ -54,6 +54,14 @@ def schedule(self): # main loop while True: + log_schedule.debug( + "schedule: current %s added %s ready %s waiting %s", + self.cur_task, + self.added_tasks, + self.ready_tasks, + self.waiting_tasks, + ) + # nothing to do anymore? if self.get_num_tasks() == 0: break @@ -81,8 +89,11 @@ def schedule(self): old_task = self.cur_task if old_task: log_schedule.debug("run: switch out %s", old_task.name) - old_task.set_state(TaskState.TS_READY) - self.ready_tasks.append(old_task) + # if cur task still running (not waiting) + # then move it to ready list + if old_task.get_state() == TaskState.TS_RUN: + old_task.set_state(TaskState.TS_READY) + self.ready_tasks.append(old_task) old_task.save_ctx() @@ -123,7 +134,8 @@ def _find_run_task(self): # keep current task task = self.cur_task log_schedule.debug("take: current task %s", task.name) - return task + if task.get_state() == TaskState.TS_READY: + return task def _make_current(self, task): self.cur_task = task @@ -134,7 +146,8 @@ def wait_task(self, task): """set the given task into wait state""" log_schedule.debug("wait_task: task %s", task.name) self.waiting_tasks.append(task) - self.reschedule(task) + task.set_state(TaskState.TS_WAIT) + self.reschedule() def wake_up_task(self, task): """take task from waiting list and allow him to schedule""" @@ -142,8 +155,9 @@ def wake_up_task(self, task): self.waiting_tasks.remove(task) # add to front self.ready_tasks.insert(0, task) + task.set_state(TaskState.TS_READY) # directly reschedule - self.reschedule(task) + self.reschedule() def add_task(self, task): """add a new task and prepare for execution. @@ -160,15 +174,19 @@ def add_task(self, task): def rem_task(self, task): # find task: is it current? removing myself... if self.cur_task == task: + log_schedule.debug("rem_task: cur_task %s", task.name) self.cur_task = None # in ready list? elif task in self.ready_tasks: + log_schedule.debug("rem_task: ready %s", task.name) self.ready_tasks.remove(task) # in waiting list? elif task in self.waiting_tasks: + log_schedule.debug("rem_task: waiting %s", task.name) self.waiting_tasks.remove(task) # in added list? elif task in self.added_tasks: + log_schedule.debug("rem_task: added %s", task.name) self.added_tasks.remove(task) # not found else: @@ -181,6 +199,6 @@ def rem_task(self, task): task.free() return True - def reschedule(self, task): + def reschedule(self): """callback from tasks to reschedule""" self.main_glet.switch() diff --git a/amitools/vamos/schedule/task.py b/amitools/vamos/schedule/task.py index 120abebd..a357a8e6 100644 --- a/amitools/vamos/schedule/task.py +++ b/amitools/vamos/schedule/task.py @@ -18,10 +18,11 @@ class TaskState(Enum): class TaskBase: """basic structure for both native and Pyhton tasks""" - def __init__(self, name, machine): + def __init__(self, name, machine, stack): self.name = name self.machine = machine self.runtime = Runtime(machine) + self.stack = stack # state self.state = TaskState.TS_INVALID self.scheduler = None @@ -42,7 +43,7 @@ def config(self, scheduler, slice_cycles): self.glet = greenlet.greenlet(self.start) def slice_hook(run_state): - self.scheduler.reschedule(self) + self.scheduler.reschedule() self.runtime.set_slice_hook(slice_cycles, slice_hook) @@ -50,9 +51,15 @@ def set_state(self, state): """the scheduler assigns a new state""" self.state = state + def get_state(self): + return self.state + def get_name(self): return self.name + def get_stack(self): + return self.stack + def get_run_state(self): """if task is running then you can get the current run state""" return self.runtime.get_current_run_state() @@ -62,15 +69,17 @@ def get_exit_code(self): def reschedule(self): """give up this tasks execution and allow the scheduler to run another task""" - self.scheduler.reschedule(self) + self.scheduler.reschedule() def forbid(self): """do not switch away from my task until permit() is called""" self.forbid_cnt += 1 + log_schedule.debug("%s: forbid (cnt=%d)", self.name, self.forbid_cnt) def permit(self): """re-enable task switching""" self.forbid_cnt -= 1 + log_schedule.debug("%s: permit (cnt=%d)", self.name, self.forbid_cnt) if self.forbid_cnt == 0: # if a schedule was triggered during the forbid state then reschedule if self.forbid_reschedule: @@ -98,11 +107,13 @@ def wait(self, sigmask): self.sigmask_wait = sigmask # go waiting. reschedule + log_schedule.debug("%s: enter wait", self.name) self.scheduler.wait_task(self) # we will return here if we were removed from waiting list # and our got mask is not empty anymore got_mask = self.sigmask_received & sigmask + log_schedule.debug("%s: leave wait. got_mask=%x", self.name, got_mask) # now reset wait mask self.sigmask_wait = 0 @@ -114,25 +125,32 @@ def get_signal(self): def set_signal(self, new_signals, sigmask): """set some of our signals""" + log_schedule.debug("%s: set_signal(%x, %x)", self.name, new_signals, sigmask) self.sigmask_received = new_signals | (self.sigmask_received & ~sigmask) # check if task is waiting for some of the signals if self.sigmask_wait != 0: got_mask = self.sigmask_received & self.sigmask_wait if got_mask != 0: + log_schedule.debug("%s: set_signal -> wake me up", self.name) self.scheduler.wake_up_task(self) # return current mask + log_schedule.debug( + "%s: set_signal -> sigmask=%x", self.name, self.sigmask_received + ) return self.sigmask_received def free(self): - """clean up task resources, e.g. stack""" - pass + self.stack.free() def start(self): """run the task until it ends. it might be interrupted by reschedule() calls""" pass - def run(self, *args, **kw_args): + def sub_run(self, *args, **kw_args): """execute m68k code in your task""" + # inject own stack if needed + if not self.runtime.is_running() and "sp" not in kw_args: + kw_args["sp"] = self.stack.get_initial_sp() return self.runtime.run(*args, **kw_args) def switch(self): @@ -140,12 +158,12 @@ def switch(self): def save_ctx(self): if self.runtime.is_running(): - log_schedule.debug("%s: save cpu context", self) + log_schedule.debug("%s: save cpu context", self.name) self.cpu_ctx = self.machine.cpu.get_cpu_context() def restore_ctx(self): if self.runtime.is_running(): - log_schedule.debug("%s: restore cpu context", self) + log_schedule.debug("%s: restore cpu context", self.name) self.machine.cpu.set_cpu_context(self.cpu_ctx) @@ -153,11 +171,10 @@ class NativeTask(TaskBase): """a task that runs native m68k code""" def __init__( - self, name, machine, init_pc, stack, start_regs=None, return_regs=None + self, name, machine, stack, init_pc, start_regs=None, return_regs=None ): - super().__init__(name, machine) + super().__init__(name, machine, stack) self.init_pc = init_pc - self.stack = stack if start_regs is None: start_regs = {} if return_regs is None: @@ -168,7 +185,7 @@ def __init__( def __repr__(self): return ( - "NativeTask(%r, init_pc=%06x, stack=%r, start_regs=%r, return_regs=%r)" + "NativeTask(%s, init_pc=%06x, stack=%r, start_regs=%r, return_regs=%r)" % ( super().__repr__(), self.init_pc, @@ -178,19 +195,19 @@ def __repr__(self): ) ) - def free(self): - if self.stack: - self.stack.free() - def start(self): pc = self.init_pc sp = self.get_init_sp() set_regs = self.start_regs get_regs = self.return_regs + log_schedule.debug("%s: start native code. pc=%06x sp=%06x", self.name, pc, sp) self.run_state = self.runtime.start( pc, sp, set_regs=set_regs, get_regs=get_regs ) self.exit_code = self.run_state.regs[REG_D0] + log_schedule.debug( + "%s: done native code. exit_code=%d", self.name, self.exit_code + ) # at the end of execution remove myself if self.scheduler: self.scheduler.rem_task(self) @@ -198,9 +215,6 @@ def start(self): def get_init_pc(self): return self.init_pc - def get_stack(self): - return self.stack - def get_init_sp(self): return self.stack.get_initial_sp() @@ -212,3 +226,34 @@ def get_return_regs(self): def get_run_result(self): return self.run_state + + +class VamosTask(TaskBase): + """a task running Python code that may use 68k code in sub runs""" + + def __init__(self, name, machine, stack, run_func=None): + """either supply a run function and an optional own stack""" + super().__init__(name, machine, stack) + self.run_func = run_func + self.stack = stack + + def __repr__(self): + return "VamosTask(%s, run_func=%r)" % (super().__repr__(), self.run_func) + + def run(self): + """overload your own code here""" + return 0 + + def start(self): + # either run run() func or method + log_schedule.debug("%s: start Python code", self.name) + if self.run_func: + self.exit_code = self.run_func(self) + else: + self.exit_code = self.run() + log_schedule.debug( + "%s: done Python code. exit_code=%d", self.name, self.exit_code + ) + # at the end of execution remove myself + if self.scheduler: + self.scheduler.rem_task(self) diff --git a/requirements-dev.txt b/requirements-dev.txt index ef475b77..2e531414 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -5,3 +5,4 @@ tox black build machine68k +greenlet diff --git a/requirements-test.txt b/requirements-test.txt index f3499cf8..4ad6e4f8 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,3 +1,4 @@ pytest pytest-benchmark machine68k +greenlet diff --git a/test/unit/schedule_scheduler.py b/test/unit/schedule_scheduler.py index 2b21dbeb..b10ece16 100644 --- a/test/unit/schedule_scheduler.py +++ b/test/unit/schedule_scheduler.py @@ -1,6 +1,6 @@ from amitools.vamos.machine import Machine from amitools.vamos.mem import MemoryAlloc -from amitools.vamos.schedule import Scheduler, NativeTask, Stack +from amitools.vamos.schedule import Scheduler, NativeTask, VamosTask, Stack from amitools.vamos.machine.opcodes import * from amitools.vamos.machine.regs import * @@ -12,13 +12,16 @@ def setup(slice_cycles=1000): return machine, sched, alloc +# ----- native tasks ----- + + def create_native_task( machine, alloc, pc, start_regs=None, return_regs=None, name=None ): if name is None: name = "task" stack = Stack.alloc(alloc, 4096) - task = NativeTask(name, machine, pc, stack, start_regs, return_regs) + task = NativeTask(name, machine, stack, pc, start_regs, return_regs) return task @@ -66,7 +69,7 @@ def cb(task): assert tasks == [task, None] -def schedule_scheduler_native_task_runner_test(): +def schedule_scheduler_native_task_subrun_test(): tasks = [] my_task = None @@ -79,7 +82,7 @@ def cb(task): pc = machine.get_scratch_begin() def trap(op, pc): - my_task.run(pc2) + my_task.sub_run(pc2) addr = machine.setup_quick_trap(trap) @@ -259,7 +262,7 @@ def schedule_scheduler_native_task_multi_wait_test(): def cb(task): tasks.append(task) - machine, sched, alloc = setup(slice_cycles=30) + machine, sched, alloc = setup() sched.set_cur_task_callback(cb) mem = alloc.get_mem() pc = machine.get_scratch_begin() @@ -301,3 +304,196 @@ def trap2(op, pc): machine.cleanup() # check that the tasks switched assert tasks == [task1, task2, task1, task2, None] + + +# ----- Vamos (Python) Tasks ----- + + +def create_vamos_task(machine, alloc, run, name=None): + if name is None: + name = "task" + stack = Stack.alloc(alloc, 4096) + task = VamosTask(name, machine, stack, run) + return task + + +def schedule_scheduler_vamos_task_simple_test(): + machine, sched, alloc = setup() + + def my_func(task): + return 42 + + # add task + task = create_vamos_task(machine, alloc, my_func) + assert sched.add_task(task) + # run scheduler + sched.schedule() + exit_code = task.get_exit_code() + assert exit_code == 42 + assert alloc.is_all_free() + machine.cleanup() + + +def schedule_scheduler_vamos_task_cur_task_hook_test(): + tasks = [] + + def cb(task): + tasks.append(task) + + machine, sched, alloc = setup() + # set cur task callback + sched.set_cur_task_callback(cb) + + def my_func(task): + return 42 + + # add task + task = create_vamos_task(machine, alloc, my_func) + assert sched.add_task(task) + assert sched.get_cur_task() is None + # run scheduler + sched.schedule() + exit_code = task.get_exit_code() + assert exit_code == 42 + assert alloc.is_all_free() + machine.cleanup() + assert tasks == [task, None] + + +def schedule_scheduler_vamos_task_subrun_test(): + tasks = [] + + def cb(task): + tasks.append(task) + + machine, sched, alloc = setup() + sched.set_cur_task_callback(cb) + mem = alloc.get_mem() + pc = machine.get_scratch_begin() + + # sub run + mem.w16(pc, op_nop) + mem.w16(pc + 2, op_rts) + + def my_func(task): + task.sub_run(pc) + return 42 + + task = create_vamos_task(machine, alloc, my_func) + + # add task + assert sched.add_task(task) + # run scheduler + sched.schedule() + exit_code = task.get_exit_code() + assert exit_code == 42 + assert alloc.is_all_free() + machine.cleanup() + assert tasks == [task, None] + + +def schedule_scheduler_vamos_task_multi_test(): + tasks = [] + + def cb(task): + tasks.append(task) + + machine, sched, alloc = setup(slice_cycles=30) + sched.set_cur_task_callback(cb) + + def task1_run(task): + # coop multi tasking! + task.reschedule() + return 42 + + def task2_run(task): + # coop multi tasking! + task.reschedule() + return 23 + + task1 = create_vamos_task(machine, alloc, task1_run, name="task1") + task2 = create_vamos_task(machine, alloc, task2_run, name="task2") + + # add task + assert sched.add_task(task1) + assert sched.add_task(task2) + # run scheduler + sched.schedule() + assert task1.get_exit_code() == 42 + assert task2.get_exit_code() == 23 + assert alloc.is_all_free() + machine.cleanup() + # check that the tasks switched + assert tasks == [task1, task2, task1, task2, None] + + +def schedule_scheduler_vamos_task_multi_forbid_test(): + tasks = [] + + def cb(task): + tasks.append(task) + + machine, sched, alloc = setup(slice_cycles=30) + sched.set_cur_task_callback(cb) + + def task1_run(task): + task.forbid() + # coop multi tasking! + # but does not switch due to forbid state + task.reschedule() + task.permit() + return 42 + + def task2_run(task): + # coop multi tasking! + task.reschedule() + return 23 + + task1 = create_vamos_task(machine, alloc, task1_run, name="task1") + task2 = create_vamos_task(machine, alloc, task2_run, name="task2") + + # add task + assert sched.add_task(task1) + assert sched.add_task(task2) + # run scheduler + sched.schedule() + assert task1.get_exit_code() == 42 + assert task2.get_exit_code() == 23 + assert alloc.is_all_free() + machine.cleanup() + # check that the tasks switched + assert tasks == [task1, task2, task1, task2, None] + + +def schedule_scheduler_vamos_task_multi_wait_test(): + tasks = [] + + def cb(task): + tasks.append(task) + + machine, sched, alloc = setup(slice_cycles=30) + sched.set_cur_task_callback(cb) + + def task1_run(task): + got = task.wait(3) + assert got == 1 + return 42 + + def task2_run(task): + task1.set_signal(1, 1) + return 23 + + task1 = create_vamos_task(machine, alloc, task1_run, name="task1") + task2 = create_vamos_task(machine, alloc, task2_run, name="task2") + + # add task + assert sched.add_task(task1) + assert sched.add_task(task2) + # run scheduler + sched.schedule() + assert task1.get_exit_code() == 42 + assert task2.get_exit_code() == 23 + assert alloc.is_all_free() + machine.cleanup() + # check that the tasks switched + assert tasks == [task1, task2, task1, task2, None] diff --git a/test/unit/schedule_task.py b/test/unit/schedule_task.py index 8378de42..f81793a0 100644 --- a/test/unit/schedule_task.py +++ b/test/unit/schedule_task.py @@ -1,5 +1,5 @@ from amitools.vamos.machine import Machine, REG_D0, op_nop, op_rts -from amitools.vamos.schedule import NativeTask, Stack +from amitools.vamos.schedule import NativeTask, VamosTask, Stack from amitools.vamos.mem import MemoryAlloc @@ -15,7 +15,15 @@ def create_native_task( if name is None: name = "task" stack = Stack.alloc(alloc, 4096) - task = NativeTask(name, machine, pc, stack, start_regs, return_regs) + task = NativeTask(name, machine, stack, pc, start_regs, return_regs) + return task + + +def create_vamos_task(machine, alloc, run, name=None): + if name is None: + name = "task" + stack = Stack.alloc(alloc, 4096) + task = VamosTask(name, machine, stack, run) return task @@ -31,3 +39,18 @@ def schedule_task_native_simple_test(): assert task.get_exit_code() == 42 assert alloc.is_all_free() machine.cleanup() + + +def schedule_task_vamos_simple_test(): + machine, alloc = setup() + mem = alloc.get_mem() + + def my_func(task): + return 42 + + task = create_vamos_task(machine, alloc, my_func) + result = task.start() + task.free() + assert task.get_exit_code() == 42 + assert alloc.is_all_free() + machine.cleanup()