diff --git a/lib/orb.ex b/lib/orb.ex index 4d5817b..b2d06e2 100644 --- a/lib/orb.ex +++ b/lib/orb.ex @@ -547,6 +547,7 @@ defmodule Orb do # TODO: rename these to orb_ prefix instead of wasm_ ? Module.put_attribute(__MODULE__, :wasm_name, __MODULE__ |> Module.split() |> List.last()) + Module.register_attribute(__MODULE__, :wasm_func_prefix, accumulate: false) Module.register_attribute(__MODULE__, :wasm_memory, accumulate: true) Module.register_attribute(__MODULE__, :wasm_globals, accumulate: true) @@ -848,6 +849,12 @@ defmodule Orb do end end + defmacro set_func_prefix(func_prefix) do + quote do + @wasm_func_prefix unquote(func_prefix) + end + end + @doc """ Declare WebAssembly globals. @@ -855,19 +862,47 @@ defmodule Orb do """ defmacro global(mode \\ :mutable, do: block) do quote do - unquote(block) + unquote(__global_block(:elixir, block)) with do import Kernel, except: [@: 1] require Orb.Global.Declare - Orb.Global.Declare.__import_mode(unquote(mode)) + Orb.Global.Declare.__import_dsl( + unquote(__MODULE__).__global_mode_mutable(unquote(mode)), + unquote(__MODULE__).__global_mode_exported(unquote(mode)) + ) - unquote(block) + unquote(__global_block(:orb, block)) end end end + def __global_mode_mutable(:readonly), do: :readonly + def __global_mode_mutable(:mutable), do: :mutable + def __global_mode_mutable(:export_readonly), do: :readonly + def __global_mode_mutable(:export_mutable), do: :mutable + def __global_mode_exported(:readonly), do: :internal + def __global_mode_exported(:mutable), do: :internal + def __global_mode_exported(:export_readonly), do: :exported + def __global_mode_exported(:export_mutable), do: :exported + + def __global_block(:elixir, items) when is_list(items) do + + end + + def __global_block(:orb, items) when is_list(items) do + require Orb.Global.Declare.DeclareDSL + + quote do + for {key, value} <- unquote(items) do + Orb.Global.Declare.DeclareDSL.register_global(key, value) + end + end + end + + def __global_block(_, block), do: block + @doc """ Declare a WebAssembly import for a function or global. """ diff --git a/lib/orb/defw_dsl.ex b/lib/orb/defw_dsl.ex index 10343dc..0ec2723 100644 --- a/lib/orb/defw_dsl.ex +++ b/lib/orb/defw_dsl.ex @@ -1,5 +1,4 @@ defmodule Orb.DefwDSL do - defmacro wasm_mode(mode) do mode = Macro.expand_literals(mode, __CALLER__) Module.put_attribute(__CALLER__.module, :wasm_mode, mode) @@ -54,20 +53,24 @@ defmodule Orb.DefwDSL do end defp define(call, visibility, result, locals, block, env) do - func_visibility = case visibility do - :internal -> :private - other -> other - end + func_visibility = + case visibility do + :internal -> :private + other -> other + end - def_kind = case visibility do - :public -> :def - :internal -> :def - :private -> :defp - end + def_kind = + case visibility do + :public -> :def + :internal -> :def + :private -> :defp + end - wasm = Orb.DSL.__define_func(call, func_visibility, [result: result, locals: locals], block, env) ex_def = define_elixir_def(call, def_kind, result, env) + wasm = + Orb.DSL.__define_func(call, func_visibility, [result: result, locals: locals], block, env) + quote do unquote(ex_def) @@ -82,20 +85,36 @@ defmodule Orb.DefwDSL do {_, meta, _} = call # arity = length(args) - def_args = case func_args do - [] -> [] - [keywords] -> (for {keyword, _type} <- keywords do - Macro.var(keyword, nil) - end) - multiple when is_list(multiple) -> - raise CompileError, line: meta[:line], file: file, description: "Cannot define function with multiple arguments, use keyword list instead." - end + def_args = + case func_args do + [] -> + [] + + [keywords] -> + for {keyword, _type} <- keywords do + Macro.var(keyword, nil) + end + + multiple when is_list(multiple) -> + raise CompileError, + line: meta[:line], + file: file, + description: + "Cannot define function with multiple arguments, use keyword list instead." + end def_call = {name, meta, def_args} quote do unquote(def_kind)(unquote(def_call)) do - Orb.Instruction.typed_call(unquote(result), unquote(name), unquote(def_args)) + Orb.Instruction.typed_call( + unquote(result), + case {@wasm_func_prefix, unquote(name)} do + {nil, name} -> name + {prefix, name} -> "#{prefix}.#{name}" + end, + unquote(def_args) + ) end end end diff --git a/lib/orb/dsl.ex b/lib/orb/dsl.ex index 0d94647..4903082 100644 --- a/lib/orb/dsl.ex +++ b/lib/orb/dsl.ex @@ -59,11 +59,15 @@ defmodule Orb.DSL do {name, args} = case Macro.decompose_call(call) do - :error -> {Orb.DSL.__expand_identifier(call, __ENV__), []} + :error -> {quote(do: Macro.inspect_atom(:literal, unquote(call))), []} other -> other end - name = name + # name = name + # name = case name_prefix do + # nil -> name + # prefix -> "#{prefix}.#{name}" + # end exported_names = case visibility do @@ -124,7 +128,11 @@ defmodule Orb.DSL do quote do %Orb.Func{ - name: unquote(name), + name: + case {@wasm_func_prefix, unquote(name)} do + {nil, name} -> name + {prefix, name} -> "#{prefix}.#{name}" + end, params: unquote(params), result: unquote(result_type), local_types: unquote(local_types), diff --git a/lib/orb/global.ex b/lib/orb/global.ex index 7b701e7..cfa4eb4 100644 --- a/lib/orb/global.ex +++ b/lib/orb/global.ex @@ -59,8 +59,10 @@ defmodule Orb.Global do end defmacro register32(mutability, exported, list) - when mutability in ~w{readonly mutable}a and - exported in ~w[internal exported]a do + + defmacro register32(mutability, exported, list) do + # when mutability in ~w{readonly mutable}a and + # exported in ~w[internal exported]a do quote do @wasm_globals (for {key, value} <- unquote(list) do Orb.Global.new32( @@ -164,10 +166,55 @@ defmodule Orb.Global do end end - defmacro __import_mode(:readonly), do: quote(do: import(ReadonlyDSL)) - defmacro __import_mode(:mutable), do: quote(do: import(MutableDSL)) - defmacro __import_mode(:export_readonly), do: quote(do: import(ExportReadonlyDSL)) - defmacro __import_mode(:export_mutable), do: quote(do: import(ExportMutableDSL)) + defmodule DeclareDSL do + import Kernel, except: [@: 1] + + defmacro register_global(name, value) do + quote do + with do + require Orb.Global + + Orb.Global.register32( + Module.get_last_attribute(__MODULE__, :wasm_global_mutability), + Module.get_last_attribute(__MODULE__, :wasm_global_exported), + [ + {unquote(name), unquote(value)} + ] + ) + end + end + end + + defmacro @{name, _meta, [arg]} do + quote do + with do + require Orb.Global + + Orb.Global.register32( + Module.get_last_attribute(__MODULE__, :wasm_global_mutability), + Module.get_last_attribute(__MODULE__, :wasm_global_exported), + [ + {unquote(name), unquote(arg)} + ] + ) + end + end + end + end + + # defmacro __import_dsl(:readonly, :internal), do: quote(do: import(ReadonlyDSL)) + # defmacro __import_dsl(:mutable, :internal), do: quote(do: import(MutableDSL)) + # defmacro __import_dsl(:readonly, :exported), do: quote(do: import(ExportReadonlyDSL)) + # defmacro __import_dsl(:mutable, :exported), do: quote(do: import(ExportMutableDSL)) + defmacro __import_dsl(mutability, exported) do + quote do + import DeclareDSL + Module.put_attribute(__MODULE__, :wasm_global_mutability, unquote(mutability)) + Module.put_attribute(__MODULE__, :wasm_global_exported, unquote(exported)) + # @wasm_global_mutability unquote(mutability) + # @wasm_global_exported unquote(exported) + end + end end defmodule DSL do diff --git a/lib/orb/memory.ex b/lib/orb/memory.ex index 44c433e..9e72e77 100644 --- a/lib/orb/memory.ex +++ b/lib/orb/memory.ex @@ -12,7 +12,7 @@ defmodule Orb.Memory do def from([]), do: nil def from(list) when is_list(list) do - case Enum.max(list) do + case Enum.sum(list) do 0 -> nil @@ -24,11 +24,15 @@ defmodule Orb.Memory do @doc """ Declare how many 64Kib pages of memory your module needs. - Can be called multiple times: the highest value is used. + Can be called multiple times, with each summed up. + + Returns the previous page count, which can be used as a start offset. """ - defmacro pages(min_count) do + defmacro pages(page_count) do quote do - @wasm_memory unquote(min_count) + start_offset = Enum.sum(@wasm_memory) + @wasm_memory unquote(page_count) + start_offset end end diff --git a/test/examples/arena.exs b/test/examples/arena.exs new file mode 100644 index 0000000..f2f619c --- /dev/null +++ b/test/examples/arena.exs @@ -0,0 +1,64 @@ +defmodule Examples.Arena do + # See https://www.rfleury.com/p/untangling-lifetimes-the-arena-allocator + + use Orb + + defmacro def(name, opts) do + # module_name = Module.concat(__MODULE__, Macro.expand_literals(name, __CALLER__)) + # Module.put_attribute(module_name, :wasm_func_prefix, module_name) + + quote do + require Orb.Memory + page_offset = Orb.Memory.pages(unquote(opts[:pages])) + + offset_global_name = + String.to_atom("#{Macro.inspect_atom(:literal, unquote(name))}.bump_offset") + + global( + do: [ + {offset_global_name, page_offset * 64 * 1024} + ] + ) + + # defmodule unquote(name) do + # use Orb + + # offset_global_name = unquote(offset_global_name) + + # # https://man7.org/linux/man-pages/man3/alloca.3.html + # defw alloc(byte_count: I32), I32.UnsafePointer do + # push(global_get(unquote(offset_global_name))) do + # # @bump_offset = I32.add(@bump_offset, size) + # end + # end + # end + + module_name = Module.concat(__MODULE__, unquote(name)) + + Module.create(module_name, quote do + use Orb + + # offset_global_name = unquote(offset_global_name) + + set_func_prefix(inspect(unquote(module_name))) + + # @wasm_func_prefix inspect(unquote(module_name)) + # Module.put_attribute(__MODULE__, :wasm_func_prefix, inspect(unquote(module_name))) + # IO.puts("put_attribute") + # IO.inspect(__MODULE__) + # IO.inspect(inspect(unquote(module_name))) + + # https://man7.org/linux/man-pages/man3/alloca.3.html + defw alloc(byte_count: I32), I32.UnsafePointer do + 0x0 + # push(global_get(unquote(offset_global_name))) do + # @bump_offset = I32.add(@bump_offset, size) + # end + end + end, unquote(Macro.Env.location(__CALLER__))) + + # alias module_name + # require module_name, as: unquote(name) + end + end +end diff --git a/test/examples/arena_test.exs b/test/examples/arena_test.exs new file mode 100644 index 0000000..431db6d --- /dev/null +++ b/test/examples/arena_test.exs @@ -0,0 +1,38 @@ +defmodule Examples.ArenaTest do + use ExUnit.Case, async: true + + Code.require_file("arena.exs", __DIR__) + alias Examples.Arena + + alias OrbWasmtime.{Instance, Wasm} + + defmodule A do + use Orb + require Arena + + Arena.def(First, pages: 2) + Arena.def(Second, pages: 3) + + Orb.include(A.First) + + defw test() do + A.First.alloc(16) + end + end + + test "add func prefixes" do + assert ~S""" + (module $A + (memory (export "memory") 5) + (global $First.bump_offset (mut i32) (i32.const 0)) + (global $Second.bump_offset (mut i32) (i32.const 131072)) + (func $Examples.ArenaTest.A.First.alloc (param $byte_count i32) (result i32) + (i32.const 0) + ) + (func $test (export "test") + (call $Examples.ArenaTest.A.First.alloc (i32.const 16)) + ) + ) + """ = A.to_wat() + end +end diff --git a/test/orb_test.exs b/test/orb_test.exs index 0c8a570..dcd4cc6 100644 --- a/test/orb_test.exs +++ b/test/orb_test.exs @@ -188,7 +188,7 @@ defmodule OrbTest do """ end - test "3 x Memory.pages/1 takes the maximum" do + test "3 x Memory.pages/1 sums each value" do defmodule TwicePages2 do use Orb @@ -199,7 +199,7 @@ defmodule OrbTest do assert to_wat(TwicePages2) == """ (module $TwicePages2 - (memory (export "memory") 3) + (memory (export "memory") 6) ) """ end