Packages
orb
0.2.1
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/ops.ex
defmodule Orb.Ops do
@moduledoc false
# See: https://webassembly.github.io/spec/core/syntax/instructions.html#numeric-instructions
@i_unary_ops ~w(clz ctz popcnt)a
@i_binary_ops ~w(add sub mul div_u div_s rem_u rem_s and or xor shl shr_u shr_s rotl rotr)a
@i_test_ops ~w(eqz)a
@i_relative_ops ~w(eq ne lt_u lt_s gt_u gt_s le_u le_s ge_u ge_s)a
# https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Numeric/Wrap
@i32_wrap_ops ~w(wrap_i64)a
# https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Numeric/Reinterpret
@i32_reinterpret_ops ~w(reinterpret_f32)a
# https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Numeric/Extend
@i64_extend_ops ~w(extend_i32_s extend_i32_u)a
# https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Numeric/Reinterpret
@i64_reinterpret_ops ~w(reinterpret_f64)a
# https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Memory/Load
@i32_load_ops ~w(load load8_u load8_s load16_s load16_u)a
@i64_load_ops @i32_load_ops ++ ~w(load32_s load32_u)a
# https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Memory/Store
@i32_store_ops ~w(store store8 store16)a
@i64_store_ops @i32_store_ops ++ ~w(store32)a
# https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Numeric/Truncate_float_to_int
@i_trunc_ops ~w(trunc_f32_s trunc_f32_u trunc_f64_s trunc_f64_u)a
@i32_ops_1 @i_unary_ops ++ @i_test_ops ++ @i_trunc_ops ++ @i32_wrap_ops ++ @i32_reinterpret_ops
@i32_ops_2 @i_binary_ops ++ @i_relative_ops
@i32_ops_all @i32_ops_1 ++ @i32_ops_2 ++ @i32_load_ops ++ @i32_store_ops
@i64_ops_1 @i_unary_ops ++
@i_test_ops ++ @i_trunc_ops ++ @i64_extend_ops ++ @i64_reinterpret_ops
@i64_ops_2 @i_binary_ops ++ @i_relative_ops
@i64_ops_all @i64_ops_1 ++ @i64_ops_2 ++ @i64_load_ops ++ @i64_store_ops
@f32_ops_1 ~w(floor ceil trunc nearest abs neg sqrt convert_i32_s convert_i32_u demote_f64 reinterpret_i32)a
@f32_ops_2 ~w(add sub mul div eq ne lt gt le ge copysign min max)a
@f64_ops_1 ~w(floor ceil trunc nearest abs neg sqrt convert_i32_s convert_i32_u convert_i64_s convert_i64_u promote_f32 reinterpret_i64)a
@f64_ops_2 ~w(add sub mul div eq ne lt gt le ge copysign min max)a
# TODO: add conversions ops https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Numeric#conversion
@integer_types ~w(i64 i32)a
@float_types ~w(f64 f32)a
@primitive_types @integer_types ++ @float_types
# TODO: remove effects
@effects ~w(unknown_effect memory_effect global_effect local_effect)a
@elixir_types [Elixir.Integer, Elixir.Float]
# @base_types @primitive_types ++ @effects ++ @elixir_types
defguard is_primitive_type(type) when type in @primitive_types
defguard is_primitive_integer_type(type) when type in @integer_types
defguard is_primitive_float_type(type) when type in @float_types
defguard is_effect(type) when type in @effects
def to_primitive_type(type) when is_primitive_type(type), do: type
def to_primitive_type(type) when is_effect(type), do: type
def to_primitive_type(type) when type in @elixir_types, do: type
# TODO: remove these as they don’t match is_primitive_type/1 above
def to_primitive_type(nil), do: raise(CompileError, description: "nil cannot be a type")
def to_primitive_type(type) when is_tuple(type) do
for nested <- Tuple.to_list(type) do
to_primitive_type(nested)
end
|> List.flatten()
|> List.to_tuple()
end
def to_primitive_type(mod) when is_atom(mod) do
Orb.CustomType.resolve!(mod) |> to_primitive_type()
end
def type_stack_count(type) when is_primitive_type(type), do: 1
def type_stack_count(type) when is_effect(type), do: 0
def type_stack_count(type) when is_atom(type), do: 1
def type_stack_count(type) when is_tuple(type), do: tuple_size(type)
def types_compatible?(Elixir.Integer, b),
do: to_primitive_type(b) in @integer_types
def types_compatible?(a, Elixir.Integer),
do: to_primitive_type(a) in @integer_types
def types_compatible?(Elixir.Float, b),
do: to_primitive_type(b) in @float_types
def types_compatible?(a, Elixir.Float),
do: to_primitive_type(a) in @float_types
def types_compatible?(a, b),
do: to_primitive_type(a) === to_primitive_type(b)
def typeof(n) when is_integer(n), do: Elixir.Integer
def typeof(n) when is_float(n), do: Elixir.Float
def typeof(%{push_type: type}), do: type
def typeof(%{type_signature: type_signature}), do: %{type_signature: type_signature}
def typeof(%{if: _}), do: :control
def typeof(str) when is_binary(str), do: :i32
# def typeof(_), do: :unknown_effect
def typeof(value, :primitive), do: typeof(value) |> to_primitive_type()
def pop_push_of(n) when is_integer(n), do: {nil, Elixir.Integer}
def pop_push_of(n) when is_float(n), do: {nil, Elixir.Float}
def pop_push_of(%{pop_type: pop_type, push_type: push_type}), do: {pop_type, push_type}
def pop_push_of(%{push_type: push_type}), do: {nil, push_type}
# Orb.Loop.Branch, Orb.Block.Branch
# Probably should add a `control: :branch`, `control: :return` field.
def pop_push_of(%{if: _}), do: {nil, nil}
# TODO: String64
def pop_push_of(str) when is_binary(str), do: {nil, Process.get(Orb.StringConstantType, :i32)}
def extract_common_type(a, b) do
{nil, push_a} = pop_push_of(a)
{nil, push_b} = pop_push_of(b)
case {to_primitive_type(push_a), to_primitive_type(push_b)} do
{same, same} -> same
{a, b} when is_effect(a) and is_effect(b) -> nil
{type, Elixir.Integer} when type in @integer_types -> type
{Elixir.Integer, type} when type in @integer_types -> type
{type, Elixir.Float} when type in @float_types -> type
{Elixir.Float, type} when type in @float_types -> type
_ -> nil
end
end
defmacro i32(arity_or_type)
defmacro i32(1), do: @i32_ops_1 |> Macro.escape()
defmacro i32(2), do: @i32_ops_2 |> Macro.escape()
defmacro i32(:load), do: @i32_load_ops |> Macro.escape()
defmacro i32(:store), do: @i32_store_ops |> Macro.escape()
defmacro i32(:all), do: @i32_ops_all |> Macro.escape()
# FIXME: conditional ops like i64.eq and i64.ge_u return i32 not i64
defmacro i64(arity_or_type)
defmacro i64(1), do: @i64_ops_1 |> Macro.escape()
defmacro i64(2), do: @i64_ops_2 |> Macro.escape()
defmacro i64(:load), do: @i64_load_ops |> Macro.escape()
defmacro i64(:store), do: @i64_store_ops |> Macro.escape()
defmacro i64(:all), do: @i64_ops_all |> Macro.escape()
defmacro f32(arity_or_type)
defmacro f32(1), do: @f32_ops_1 |> Macro.escape()
defmacro f32(2), do: @f32_ops_2 |> Macro.escape()
defmacro f64(arity_or_type)
defmacro f64(1), do: @f64_ops_1 |> Macro.escape()
defmacro f64(2), do: @f64_ops_2 |> Macro.escape()
defp i32_arity(op) when op in @i32_ops_1, do: 1
defp i32_arity(op) when op in @i32_ops_2, do: 2
defp i64_arity(op) when op in @i64_ops_1, do: 1
defp i64_arity(op) when op in @i64_ops_2, do: 2
defp f32_arity(op) when op in @f32_ops_1, do: 1
defp f32_arity(op) when op in @f32_ops_2, do: 2
defp f64_arity(op) when op in @f64_ops_1, do: 1
defp f64_arity(op) when op in @f64_ops_2, do: 2
defp i32_param_type(op, param_index)
defp i32_param_type(:const, 0), do: Elixir.Integer
defp i32_param_type(op, 0) when op in @i32_load_ops, do: :i32
defp i32_param_type(op, i) when op in @i32_store_ops and i in [0, 1], do: :i32
defp i32_param_type(op, 0) when op in @i_trunc_ops, do: :f32
defp i32_param_type(op, 0) when op in @i_unary_ops, do: :i32
defp i32_param_type(op, 0) when op in @i_test_ops, do: :i32
defp i32_param_type(op, 0) when op in @i32_wrap_ops, do: :i64
defp i32_param_type(op, i) when op in @i_binary_ops and i in [0, 1], do: :i32
defp i32_param_type(op, i) when op in @i_relative_ops and i in [0, 1], do: :i32
defp i32_param_type(_, _), do: :error
defp i64_param_type(op, param_index)
defp i64_param_type(:const, 0), do: Elixir.Integer
defp i64_param_type(op, 0) when op in @i64_load_ops, do: :i64
defp i64_param_type(op, i) when op in @i64_store_ops and i in [0, 1], do: :i64
defp i64_param_type(op, 0) when op in @i_trunc_ops, do: :f64
defp i64_param_type(op, 0) when op in @i_unary_ops, do: :i64
defp i64_param_type(op, 0) when op in @i_test_ops, do: :i64
defp i64_param_type(op, 0) when op in @i64_extend_ops, do: :i32
defp i64_param_type(op, i) when op in @i_binary_ops and i in [0, 1], do: :i64
defp i64_param_type(op, i) when op in @i_relative_ops and i in [0, 1], do: :i64
defp i64_param_type(_, _), do: :error
defp f32_param_type(op, param_index)
defp f32_param_type(:const, 0), do: Elixir.Float
defp f32_param_type(:convert_i32_s, 0), do: :i32
defp f32_param_type(:convert_i32_u, 0), do: :i32
defp f32_param_type(:demote_f64, 0), do: :f64
defp f32_param_type(op, 0) when op in @f32_ops_1, do: :f32
defp f32_param_type(op, i) when op in @f32_ops_2 and i in [0, 1], do: :f32
defp f32_param_type(_, _), do: :error
defp f64_param_type(op, param_index)
defp f64_param_type(:const, 0), do: Elixir.Float
defp f64_param_type(:convert_i32_s, 0), do: :i32
defp f64_param_type(:convert_i32_u, 0), do: :i32
defp f64_param_type(:convert_i64_s, 0), do: :i64
defp f64_param_type(:convert_i64_u, 0), do: :i64
defp f64_param_type(:promote_f32, 0), do: :f32
defp f64_param_type(op, 0) when op in @f64_ops_1, do: :f64
defp f64_param_type(op, i) when op in @f64_ops_2 and i in [0, 1], do: :f64
defp f64_param_type(_, _), do: :error
def i32_param_type!(op, param_index) do
case i32_param_type(op, param_index) do
:error ->
raise ArgumentError,
"WebAssembly instruction i32.#{op}/#{i32_arity(op)} does not accept a #{nth(param_index)} argument."
type ->
type
end
end
def i64_param_type!(op, param_index) do
case i64_param_type(op, param_index) do
:error ->
raise ArgumentError,
"WebAssembly instruction i64.#{op}/#{i64_arity(op)} does not accept a #{nth(param_index)} argument."
type ->
type
end
end
def f32_param_type!(op, param_index) do
case f32_param_type(op, param_index) do
:error ->
raise ArgumentError,
"WebAssembly instruction f32.#{op}/#{f32_arity(op)} does not accept a #{nth(param_index)} argument."
type ->
type
end
end
def f64_param_type!(op, param_index) do
case f64_param_type(op, param_index) do
:error ->
raise ArgumentError,
"WebAssembly instruction f64.#{op}/#{f64_arity(op)} does not accept a #{nth(param_index)} argument."
type ->
type
end
end
def param_type!(:i32, op, param_index), do: i32_param_type!(op, param_index)
def param_type!(:i64, op, param_index), do: i64_param_type!(op, param_index)
def param_type!(:f32, op, param_index), do: f32_param_type!(op, param_index)
def param_type!(:f64, op, param_index), do: f64_param_type!(op, param_index)
defp nth(0), do: "1st"
defp nth(1), do: "2nd"
defp nth(2), do: "3rd"
defp nth(n), do: "#{n + 1}th"
def doc(type, op)
def doc(:i64, :extend_i32_u), do: "Convert unsigned i32 to unsigned i64."
def doc(:i64, :extend_i32_s), do: "Convert signed i32 to signed i64."
def doc(_type, _op), do: nil
end