Skip to content

Commit

Permalink
Compile to .wasm binary: memory, data, exports!
Browse files Browse the repository at this point in the history
  • Loading branch information
RoyalIcing committed Jul 23, 2024
1 parent 4399bc0 commit 3f2a551
Show file tree
Hide file tree
Showing 17 changed files with 275 additions and 67 deletions.
2 changes: 1 addition & 1 deletion lib/orb.ex
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,7 @@ defmodule Orb do
%VariableReference{global_or_local: :local, identifier: identifier},
context
) do
[0x20, uleb128(Orb.ToWasm.Context.fetch_local_index!(context, identifier))]
[0x20, leb128_u(Orb.ToWasm.Context.fetch_local_index!(context, identifier))]
end
end
end
Expand Down
18 changes: 18 additions & 0 deletions lib/orb/constants.ex
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,22 @@ defmodule Orb.Constants do
]
end
end

defimpl Orb.ToWasm do
def to_wasm(
%Orb.Constants{lookup_table: lookup_table},
context
) do
for {string, offset} <- lookup_table do
[
# active
0x0,
Orb.Instruction.Const.new(:i32, offset) |> Orb.ToWasm.to_wasm(context),
# end opcode
0x0B,
Orb.ToWasm.Helpers.sized(string)
]
end
end
end
end
2 changes: 1 addition & 1 deletion lib/orb/dsl/defw.ex
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ defmodule Orb.DSL.Defw do

quote do
unquote(def_kind)(unquote(def_call)) do
Orb.Instruction.typed_call(
Orb.Instruction.Call.new(
unquote(result_type),
unquote(param_types),
case {@wasm_func_prefix, unquote(name)} do
Expand Down
3 changes: 2 additions & 1 deletion lib/orb/func.ex
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ defmodule Orb.Func do
%Orb.Func{params: params, body: body, local_types: local_types},
context
) do
local_decls = for {_, type} <- local_types, do: [0x01, Orb.Func.Param.to_wasm_type(type)]
local_decls =
for {_, type} <- local_types, do: [0x01, to_wasm_type(type)]

param_types = for param <- params, do: {param.name, param.type}
context = Context.set_local_get_types(context, param_types ++ local_types)
Expand Down
4 changes: 2 additions & 2 deletions lib/orb/instruction.ex
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ defmodule Orb.Instruction do
},
_
) do
[type_const(type), uleb128(number)]
[type_const(type), leb128_s(number)]
end

def to_wasm(
Expand Down Expand Up @@ -539,7 +539,7 @@ defmodule Orb.Instruction do
[
for(operand <- operands, do: Orb.ToWasm.to_wasm(operand, context)),
0x21,
uleb128(Orb.ToWasm.Context.fetch_local_index!(context, identifier))
leb128_u(Orb.ToWasm.Context.fetch_local_index!(context, identifier))
]
end

Expand Down
76 changes: 76 additions & 0 deletions lib/orb/instruction/call.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
defmodule Orb.Instruction.Call do
@moduledoc false
defstruct [:push_type, :func_identifier, :args]

defguardp is_type_compatible(type)
when is_atom(type) or
(is_tuple(type) and tuple_size(type) >= 2)

def new(result_type, param_types, f, args) when is_list(param_types) do
param_types =
case param_types do
[] -> nil
[single] -> single
multiple -> List.to_tuple(multiple)
end

new(result_type, param_types, f, args)
end

def new(result_type, param_types, func_identifier, args)
when (is_nil(result_type) or is_type_compatible(result_type)) and
(is_nil(param_types) or is_type_compatible(param_types)) and
is_list(args) do
args =
case {param_types, args} do
{nil, []} ->
[]

{type, [value]} when is_atom(type) ->
[Orb.Instruction.Const.wrap(type, value)]

{types, values} when tuple_size(types) === length(values) ->
for {type, value} <- Enum.zip(Tuple.to_list(types), values) do
Orb.Instruction.Const.wrap(type, value)
end
end

%__MODULE__{
push_type: result_type,
func_identifier: func_identifier,
args: args
}
end

defimpl Orb.ToWat do
def to_wat(%Orb.Instruction.Call{func_identifier: func_identifier, args: args}, indent) do
[
indent,
"(call $",
to_string(func_identifier),
for(arg <- args, do: [" ", Orb.ToWat.to_wat(arg, "")]),
")"
]
end
end

defimpl Orb.ToWasm do
def to_wasm(
%Orb.Instruction.Call{
func_identifier: func_identifier,
args: args
},
context
) do
function_index = Orb.ToWasm.Context.fetch_func_index!(context, func_identifier)

[
for arg <- args do
Orb.ToWasm.to_wasm(arg, context)
end,
0x10,
function_index
]
end
end
end
2 changes: 1 addition & 1 deletion lib/orb/instruction/const.ex
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ defmodule Orb.Instruction.Const do
type_const(type),
cond do
is_integer(number) ->
uleb128(number)
leb128_s(number)

is_float(number) and type === :f32 ->
<<number::32-float-little>>
Expand Down
29 changes: 24 additions & 5 deletions lib/orb/leb.ex
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
defmodule Orb.Leb do
@moduledoc false

def uleb128(0), do: [0]
def uleb128(n), do: uleb128(n, [])
def leb128_u(0), do: [0]
def leb128_u(n), do: leb128_u(n, [])

defp uleb128(0, bytes), do: bytes |> Enum.reverse()
defp leb128_u(0, bytes), do: bytes |> Enum.reverse()

defp uleb128(value, bytes) do
defp leb128_u(value, bytes) do
import Bitwise

byte = value &&& 0x7F
Expand All @@ -21,6 +21,25 @@ defmodule Orb.Leb do
byte ||| 0x80
end

uleb128(value, [byte | bytes])
leb128_u(value, [byte | bytes])
end

def leb128_s(0), do: [0]
def leb128_s(n), do: leb128_s(n, [])

defp leb128_s(value, bytes) do
import Bitwise

byte = value &&& 0x7F
value = value >>> 7

cond do
(value === 0 and (byte &&& 0x40) === 0) or (value === -1 and (byte &&& 0x40) !== 0) ->
[byte | bytes] |> Enum.reverse()

true ->
byte = byte ||| 0x80
leb128_s(value, [byte | bytes])
end
end
end
4 changes: 2 additions & 2 deletions lib/orb/loop.ex
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ defmodule Orb.Loop do
) do
index = Orb.ToWasm.Context.fetch_loop_identifier_index!(context, identifier)

[0x0C, Leb.uleb128(index)]
[0x0C, Leb.leb128_u(index)]
end

def to_wasm(
Expand All @@ -97,7 +97,7 @@ defmodule Orb.Loop do
[
Orb.ToWasm.to_wasm(condition, context),
0x0D,
Leb.uleb128(index)
Leb.leb128_u(index)
]
end
end
Expand Down
20 changes: 19 additions & 1 deletion lib/orb/memory.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ defmodule Orb.Memory do
Work with memory: load, store, declare pages & initial data.
"""

defstruct name: "", min: 0, exported?: false
defstruct name: "", min: 0, exported_name: "memory"

@doc false
def new(page_definitions, constants)
Expand Down Expand Up @@ -199,4 +199,22 @@ defmodule Orb.Memory do
]
end
end

defimpl Orb.ToWasm do
import Orb.Leb

def to_wasm(
%Orb.Memory{
min: min
},
_context
) do
[
case min do
nil -> [0x0, 0x0]
min when min >= 0 -> [0x0, leb128_u(min)]
end
]
end
end
end
67 changes: 49 additions & 18 deletions lib/orb/module_definition.ex
Original file line number Diff line number Diff line change
Expand Up @@ -186,48 +186,79 @@ defmodule Orb.ModuleDefinition do
table_size: _table_size,
imports: _imports,
globals: _globals,
memory: _memory,
constants: _constants,
memory: memory,
constants: constants,
body: mod_body,
data: _data
},
context
) do
funcs = Enum.with_index(for f = %Orb.Func{} <- mod_body, do: f)

uniq_func_types =
for({f, _index} <- funcs, do: func_type_tuple(f))

func_defs =
for {_f, index} <- funcs do
index |> uleb128()
end
context =
context
|> Orb.ToWasm.Context.set_func_name_index_lookup(
Map.new(funcs, fn {f, index} -> {f.name, index} end)
)

# uniq_func_types =
# for({f, _index} <- funcs, do: func_type_tuple(f))
# |> Enum.uniq()

# func_defs =
# for {f, _index} <- funcs do
# type_to_find = func_type_tuple(f)

# Enum.find_index(uniq_func_types, fn type -> type === type_to_find end)
# |> uleb128()
# for {_f, index} <- funcs do
# leb128_u(index)
# end

uniq_func_types =
for({f, _index} <- funcs, do: func_type_tuple(f))
|> Enum.uniq()

func_defs =
for {f, _index} <- funcs do
type_to_find = func_type_tuple(f)

Enum.find_index(uniq_func_types, fn type -> type === type_to_find end)
|> leb128_u()
end

export_funcs =
for {%Orb.Func{exported_names: names}, index} <- funcs, name <- names do
encode_export_func(name, index)
end

export_memories =
case memory do
nil ->
[]

%{exported_name: exported_name} ->
[
[
sized(exported_name),
0x02,
leb128_u(0)
]
]
end

func_code = for {f, _index} <- funcs, do: Orb.ToWasm.to_wasm(f, context)

[
@wasm_prefix,
section(:type, vec(for {p, r} <- uniq_func_types, do: encode_func_type(p, r))),
section(:function, vec(func_defs)),
section(:export, vec(export_funcs)),
section(:code, vec(func_code))
if memory do
section(:memory, vec([Orb.ToWasm.to_wasm(memory, context)]))
else
[]
end,
section(:export, vec(export_memories ++ export_funcs)),
# section(:data_count, vec([])),
section(:code, vec(func_code)),
case Orb.ToWasm.to_wasm(constants, context) do
[] -> []
constants_wasm -> section(:data, vec(constants_wasm))
end
]
end

Expand Down Expand Up @@ -282,9 +313,9 @@ defmodule Orb.ModuleDefinition do
defp section(:export, bytes), do: section(0x07, bytes)
defp section(:start, bytes), do: section(0x08, bytes)
defp section(:element, bytes), do: section(0x09, bytes)
defp section(:data_count, bytes), do: section(0x0C, bytes)
defp section(:code, bytes), do: section(0x0A, bytes)
defp section(:data, bytes), do: section(0x0B, bytes)
defp section(:data_count, bytes), do: section(0x0C, bytes)

defp section(id, bytes) do
[
Expand Down
3 changes: 3 additions & 0 deletions lib/orb/ops.ex
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ defmodule Orb.Ops do
@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
Expand All @@ -53,6 +54,7 @@ defmodule Orb.Ops do
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: :nop
# TODO: remove :nop and :trap
def to_primitive_type(:nop), do: :nop
def to_primitive_type(:trap), do: :trap
def to_primitive_type(%{result: result}), do: result
Expand All @@ -61,6 +63,7 @@ defmodule Orb.Ops do
for nested <- Tuple.to_list(type) do
to_primitive_type(nested)
end
|> List.flatten()
|> List.to_tuple()
end

Expand Down
Loading

0 comments on commit 3f2a551

Please sign in to comment.