279 lines
8.4 KiB
Elixir
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
|