farmbot_os/farmbot_celery_script/lib/farmbot_celery_script/run_time/farm_proc.ex

279 lines
8.4 KiB
Elixir

defmodule Farmbot.CeleryScript.RunTime.FarmProc do
@moduledoc """
FarmProc is a _single_ running unit of execution. It must be
`step`ed. It manages IO, but does no sort of management.
"""
alias Farmbot.CeleryScript.RunTime.{
AST,
FarmProc,
SysCallHandler,
InstructionSet
}
import Farmbot.CeleryScript.Utils
alias Farmbot.CeleryScript.AST
alias AST.Heap
@max_reduction_count 1000
defstruct sys_call_fun: nil,
zero_page: nil,
reduction_count: 0,
pc: nil,
rs: [],
io_latch: nil,
io_result: nil,
crash_reason: nil,
status: :ok,
heap: %{},
ref: nil
@typedoc "Program counter"
@type heap_address :: Address.t()
@typedoc "Page address register"
@type page :: Address.t()
@typedoc "Possible values of the status attribute."
@type status_enum :: :ok | :done | :crashed | :waiting
@type t :: %FarmProc{
ref: reference(),
crash_reason: nil | String.t(),
heap: %{Address.t() => Heap.t()},
io_latch: nil | pid,
io_result: nil | any,
pc: Pointer.t(),
reduction_count: 0 | pos_integer(),
rs: [Pointer.t()],
status: status_enum(),
sys_call_fun: Farmbot.CeleryScript.RunTime.SysCallHandler.sys_call_fun(),
zero_page: Address.t()
}
@typedoc false
@type new :: %Farmbot.CeleryScript.RunTime.FarmProc{
ref: reference(),
crash_reason: nil,
heap: %{Address.t() => Heap.t()},
io_latch: nil,
io_result: nil,
pc: Pointer.t(),
reduction_count: 0,
rs: [],
status: :ok,
sys_call_fun: Farmbot.CeleryScript.RunTime.SysCallHandler.sys_call_fun(),
zero_page: Address.t()
}
@spec new(Farmbot.CeleryScript.RunTime.SysCallHandler.sys_call_fun(), page, Heap.t()) :: new()
def new(sys_call_fun, %Address{} = page, %Heap{} = heap)
when is_function(sys_call_fun) do
pc = Pointer.new(page, addr(1))
%FarmProc{
ref: make_ref(),
status: :ok,
zero_page: page,
pc: pc,
sys_call_fun: sys_call_fun,
heap: %{page => heap}
}
end
@spec new_page(FarmProc.t(), page, Heap.t()) :: FarmProc.t()
def new_page(
%FarmProc{} = farm_proc,
%Address{} = page_num,
%Heap{} = heap_contents
) do
new_heap = Map.put(farm_proc.heap, page_num, heap_contents)
%FarmProc{farm_proc | heap: new_heap}
end
@spec get_zero_page(FarmProc.t()) :: page
def get_zero_page(%FarmProc{} = farm_proc),
do: farm_proc.zero_page
@spec has_page?(FarmProc.t(), page) :: boolean()
def has_page?(%FarmProc{} = farm_proc, %Address{} = page),
do: Map.has_key?(farm_proc.heap, page)
@spec step(FarmProc.t()) :: FarmProc.t() | no_return
def step(%FarmProc{status: :crashed} = farm_proc),
do: exception(farm_proc, "Tried to step with crashed process!")
def step(%FarmProc{status: :done} = farm_proc), do: farm_proc
def step(%FarmProc{reduction_count: c} = proc) when c >= @max_reduction_count,
do: exception(proc, "Too many reductions!")
def step(%FarmProc{status: :waiting} = farm_proc) do
case SysCallHandler.get_status(farm_proc.io_latch) do
:ok ->
farm_proc
:complete ->
io_result = SysCallHandler.get_results(farm_proc.io_latch)
set_status(farm_proc, :ok)
|> set_io_latch_result(io_result)
|> remove_io_latch()
|> step()
end
end
def step(%FarmProc{} = farm_proc) do
pc_ptr = get_pc_ptr(farm_proc)
kind = get_kind(farm_proc, pc_ptr)
# TODO Connor 07-31-2018: why do i have to load the module here?
available? =
Code.ensure_loaded?(InstructionSet) and
function_exported?(InstructionSet, kind, 1)
unless available? do
exception(farm_proc, "No implementation for: #{kind}")
end
farm_proc = %FarmProc{
farm_proc
| reduction_count: farm_proc.reduction_count + 1
}
# IO.puts "executing: [#{pc_ptr.page_address}, #{inspect pc_ptr.heap_address}] #{kind}"
apply(InstructionSet, kind, [farm_proc])
end
@spec get_pc_ptr(FarmProc.t()) :: Pointer.t()
def get_pc_ptr(%FarmProc{pc: pc}), do: pc
@spec set_pc_ptr(FarmProc.t(), Pointer.t()) :: FarmProc.t()
def set_pc_ptr(%FarmProc{} = farm_proc, %Pointer{} = pc),
do: %FarmProc{farm_proc | pc: pc}
def set_io_latch(%FarmProc{} = farm_proc, pid) when is_pid(pid),
do: %FarmProc{farm_proc | io_latch: pid}
def set_io_latch_result(%FarmProc{} = farm_proc, result),
do: %FarmProc{farm_proc | io_result: result}
@spec clear_io_result(FarmProc.t()) :: FarmProc.t()
def clear_io_result(%FarmProc{} = farm_proc),
do: %FarmProc{farm_proc | io_result: nil}
@spec remove_io_latch(FarmProc.t()) :: FarmProc.t()
def remove_io_latch(%FarmProc{} = farm_proc),
do: %FarmProc{farm_proc | io_latch: nil}
@spec get_heap_by_page_index(FarmProc.t(), page) :: Heap.t() | no_return
def get_heap_by_page_index(%FarmProc{heap: heap} = proc, %Address{} = page) do
heap[page] || exception(proc, "no page: #{inspect(page)}")
end
@spec get_return_stack(FarmProc.t()) :: [Pointer.t()]
def get_return_stack(%FarmProc{rs: rs}), do: rs
@spec get_kind(FarmProc.t(), Pointer.t()) :: atom
def get_kind(%FarmProc{} = farm_proc, %Pointer{} = ptr) do
get_cell_attr(farm_proc, ptr, Heap.kind())
end
@spec get_parent(FarmProc.t(), Pointer.t()) :: Address.t()
def get_parent(%FarmProc{} = farm_proc, %Pointer{} = ptr) do
get_cell_attr(farm_proc, ptr, Heap.parent())
end
@spec get_status(FarmProc.t()) :: status_enum()
def get_status(%FarmProc{status: status}), do: status
@spec set_status(FarmProc.t(), status_enum()) :: FarmProc.t()
def set_status(%FarmProc{} = farm_proc, status) do
%FarmProc{farm_proc | status: status}
end
@spec get_body_address(FarmProc.t(), Pointer.t()) :: Pointer.t()
def get_body_address(
%FarmProc{} = farm_proc,
%Pointer{} = here_address
) do
get_cell_attr_as_pointer(farm_proc, here_address, Heap.body())
end
@spec get_next_address(FarmProc.t(), Pointer.t()) :: Pointer.t()
def get_next_address(
%FarmProc{} = farm_proc,
%Pointer{} = here_address
) do
get_cell_attr_as_pointer(farm_proc, here_address, Heap.next())
end
@spec get_cell_attr(FarmProc.t(), Pointer.t(), atom) ::
Address.t() | String.t() | number() | boolean() | atom()
def get_cell_attr(
%FarmProc{} = farm_proc,
%Pointer{} = location,
field
) do
cell = get_cell_by_address(farm_proc, location)
cell[field] ||
exception(farm_proc, "no field called: #{field} at #{inspect(location)}")
end
@spec get_cell_attr_as_pointer(FarmProc.t(), Pointer.t(), atom) :: Pointer.t()
def get_cell_attr_as_pointer(
%FarmProc{} = farm_proc,
%Pointer{} = location,
field
) do
%Address{} = data = get_cell_attr(farm_proc, location, field)
Pointer.new(location.page_address, data)
end
@spec push_rs(FarmProc.t(), Pointer.t()) :: FarmProc.t()
def push_rs(%FarmProc{} = farm_proc, %Pointer{} = ptr) do
new_rs = [ptr | FarmProc.get_return_stack(farm_proc)]
%FarmProc{farm_proc | rs: new_rs}
end
@spec pop_rs(FarmProc.t()) :: {Pointer.t(), FarmProc.t()}
def pop_rs(%FarmProc{rs: rs} = farm_proc) do
case rs do
[hd | new_rs] ->
{hd, %FarmProc{farm_proc | rs: new_rs}}
[] ->
{Pointer.null(FarmProc.get_zero_page(farm_proc)), farm_proc}
end
end
@spec get_crash_reason(FarmProc.t()) :: String.t() | nil
def get_crash_reason(%FarmProc{} = crashed),
do: crashed.crash_reason
@spec set_crash_reason(FarmProc.t(), String.t()) :: FarmProc.t()
def set_crash_reason(%FarmProc{} = crashed, reason)
when is_binary(reason) do
%FarmProc{crashed | crash_reason: reason}
end
@spec is_null_address?(Address.t() | Pointer.t()) :: boolean()
def is_null_address?(%Address{value: 0}), do: true
def is_null_address?(%Address{}), do: false
def is_null_address?(%Pointer{heap_address: %Address{value: 0}}),
do: true
def is_null_address?(%Pointer{}), do: false
@spec get_cell_by_address(FarmProc.t(), Pointer.t()) :: map | no_return
def get_cell_by_address(
%FarmProc{} = farm_proc,
%Pointer{page_address: page, heap_address: %Address{} = ha}
) do
get_heap_by_page_index(farm_proc, page)[ha] ||
exception(farm_proc, "bad address")
end
end