Packages
orb
0.1.0
0.2.2
0.2.1
0.2.0
0.1.1
0.1.0
0.0.51
0.0.49
0.0.48
0.0.47
0.0.46
0.0.45
0.0.44
0.0.43
0.0.42
0.0.41
0.0.40
0.0.39
0.0.38
0.0.37
0.0.36
0.0.35
0.0.34
0.0.33
0.0.32
0.0.31
0.0.30
0.0.28
0.0.27
0.0.26
0.0.25
0.0.24
0.0.23
0.0.22
0.0.21
0.0.20
0.0.19
0.0.18
0.0.17
0.0.16
0.0.15
0.0.14
0.0.13
0.0.12
retired
0.0.11
0.0.10
0.0.9
0.0.8
0.0.7
0.0.6
0.0.5
0.0.4
0.0.3
0.0.2
0.0.1
DSL for WebAssembly
Current section
Files
Jump to
Current section
Files
lib/orb/instruction_sequence.ex
defmodule Orb.InstructionSequence do
@moduledoc false
defstruct pop_type: nil,
push_type: nil,
# control: nil,
body: [],
contains:
%{
# locals_effected: %{},
# globals_effected: %{},
# unknown_effect: false,
# i32: false,
# i64: false,
# f32: false,
# f64: false
},
locals: []
require Orb.Ops
alias Orb.Ops
alias Orb.Constants
@doc ~S"""
Creates a sequence of instructions from a list.
## Examples
iex> Orb.InstructionSequence.new([Orb.InstructionSequence.empty()])
Orb.InstructionSequence.empty()
iex> Orb.InstructionSequence.new([Orb.Instruction.i32(:const, 1)])
%Orb.InstructionSequence{push_type: :i32, body: [
Orb.Instruction.i32(:const, 1)
]}
iex> Orb.InstructionSequence.new([%Orb.Nop{}])
%Orb.InstructionSequence{push_type: nil, body: [
%Orb.Nop{}
]}
iex> Orb.InstructionSequence.new([Orb.Instruction.i32(:const, 1), Orb.Instruction.f32(:const, 2.0)])
%Orb.InstructionSequence{push_type: {:i32, :f32}, body: [
Orb.Instruction.i32(:const, 1),
Orb.Instruction.f32(:const, 2.0),
]}
iex> Orb.InstructionSequence.new([Orb.Control.Return.new(Orb.Instruction.i32(:const, 1))])
%Orb.InstructionSequence{push_type: nil, body: [
Orb.Control.Return.new(Orb.Instruction.i32(:const, 1))
]}
"""
def new([instruction_sequence = %__MODULE__{}]), do: instruction_sequence
# def new([instruction = %{push_type: _}]), do: instruction
def new(instructions) when is_list(instructions) do
push_type = do_pop_push_type([], instructions)
local_types =
do_local_types(instructions, [])
new(push_type, instructions, locals: local_types)
end
def new(push_type, instructions, opts \\ []) when is_list(instructions) do
%__MODULE__{
push_type: push_type,
body: instructions,
locals: Keyword.get(opts, :locals, [])
}
end
def empty(), do: %__MODULE__{}
defp type_to_list(type)
defp type_to_list(nil), do: []
defp type_to_list(:nop), do: []
defp type_to_list(:trap), do: []
defp type_to_list(type) when is_atom(type), do: [type]
defp type_to_list(type) when is_tuple(type), do: Tuple.to_list(type) |> Enum.reverse()
defp do_pop_push_type(stack, instructions)
defp do_pop_push_type([], []), do: nil
defp do_pop_push_type([single], []), do: single
defp do_pop_push_type(stack, []), do: stack |> Enum.reverse() |> List.to_tuple()
# If there’s a (return …), then nothing becomes pushed to the stack.
# TODO: check returned stack type against surrounding func’s type.
# This might have to be comapred again the one stored
# under Process dict’s Orb.Func.ReturnType entry.
defp do_pop_push_type(_, [%Orb.Control.Return{} | _]), do: nil
defp do_pop_push_type([], [head | rest]) do
case Ops.pop_push_of(head) do
{nil, push} ->
do_pop_push_type(type_to_list(push), rest)
{pop, _push} ->
raise "Cannot pop #{pop} from empty stack."
end
end
defp do_pop_push_type(stack, [head | rest]) do
case {Ops.pop_push_of(head), stack} do
{{pop, push}, [pop | stack]} ->
do_pop_push_type(type_to_list(push) ++ stack, rest)
{{nil, push}, stack} ->
do_pop_push_type(type_to_list(push) ++ stack, rest)
{{pop, push}, [expected_type | stack]} ->
cond do
Ops.is_primitive_type(expected_type) and Ops.to_primitive_type(pop) === expected_type ->
do_pop_push_type(type_to_list(push) ++ stack, rest)
true ->
raise "Cannot pop #{pop} from stack, expected type #{expected_type}."
end
end
end
defp do_local_types(instructions, local_types)
defp do_local_types([], local_types), do: local_types
defp do_local_types([%{locals: locals} | rest], local_types) do
local_types = merge_locals(local_types, locals)
do_local_types(rest, local_types)
end
defp do_local_types([%{body: %__MODULE__{locals: locals}} | rest], local_types) do
local_types = merge_locals(local_types, locals)
do_local_types(rest, local_types)
end
defp do_local_types([_ | rest], local_types), do: do_local_types(rest, local_types)
defp merge_locals(a, b) do
# TODO: raise on duplicate?
Keyword.merge(a, b)
end
def concat_locals(instruction_sequence = %__MODULE__{}, locals) do
new_locals = merge_locals(instruction_sequence.locals, locals)
%{instruction_sequence | locals: new_locals}
end
@doc ~S"""
Concatenates instructions in the `second` with those in `first`.
## Examples
iex> Orb.InstructionSequence.concat(Orb.InstructionSequence.empty(), Orb.InstructionSequence.empty())
Orb.InstructionSequence.empty()
iex> Orb.InstructionSequence.concat(nil, nil)
Orb.InstructionSequence.empty()
iex> Orb.InstructionSequence.concat(Orb.InstructionSequence.new([Orb.Instruction.i32(:const, 1)]), Orb.InstructionSequence.new([Orb.Instruction.f32(:const, 2.0)]))
%Orb.InstructionSequence{push_type: {:i32, :f32}, body: [
Orb.Instruction.i32(:const, 1),
Orb.Instruction.f32(:const, 2.0)
]}
iex> Orb.InstructionSequence.concat(Orb.InstructionSequence.new([Orb.Instruction.i32(:const, 1)]), nil)
%Orb.InstructionSequence{push_type: :i32, body: [
Orb.Instruction.i32(:const, 1)
]}
"""
def concat(first, second)
def concat(nil, nil), do: empty()
def concat(nil, seq = %__MODULE__{}), do: seq
def concat(seq = %__MODULE__{}, nil), do: seq
def concat(first = %__MODULE__{}, second = %__MODULE__{}) do
new(first.body ++ second.body)
end
def expand(%__MODULE__{} = func) do
body = func.body |> Enum.map(&Constants.expand_if_needed/1)
%{func | body: body}
end
defimpl Orb.ToWat do
alias Orb.ToWat.Instructions
def to_wat(
%Orb.InstructionSequence{body: instructions},
indent
) do
for instruction <- instructions do
case instruction do
%Orb.InstructionSequence{} ->
to_wat(instruction, indent)
tuple when is_tuple(tuple) ->
tuple
|> Tuple.to_list()
|> Enum.map(fn instruction ->
[Instructions.do_wat(instruction, indent), "\n"]
end)
_ ->
# [Orb.ToWat.to_wat(instruction, indent), "\n"]
case Instructions.do_wat(instruction, indent) do
# "" -> ""
# [] -> ""
output -> [output, "\n"]
end
end
end
end
end
defimpl Orb.ToWasm do
def to_wasm(
%Orb.InstructionSequence{body: instructions},
context
) do
for instruction <- instructions do
case instruction do
tuple when is_tuple(tuple) ->
tuple
|> Tuple.to_list()
|> Enum.map(fn instruction ->
Orb.ToWasm.to_wasm(instruction, context)
end)
instruction ->
Orb.ToWasm.to_wasm(instruction, context)
end
end
end
end
defimpl Orb.TypeNarrowable do
def type_narrow_to(
%Orb.InstructionSequence{push_type: current_type, body: [single]} = seq,
narrower_type
) do
case Ops.types_compatible?(current_type, narrower_type) do
true ->
%{
seq
| push_type: narrower_type,
body: [Orb.TypeNarrowable.type_narrow_to(single, narrower_type)]
}
false ->
seq
end
end
def type_narrow_to(%Orb.InstructionSequence{} = seq, _), do: seq
end
end