From 6308c8bbd99db5a407cdc3c81381108e6c10d965 Mon Sep 17 00:00:00 2001 From: Patrick Smith Date: Mon, 30 Oct 2023 22:54:05 +1100 Subject: [PATCH] Implement bump alloc for Arena --- lib/orb/memory.ex | 3 ++ test/examples/arena.exs | 58 +++++++++++++++++++++++---------- test/examples/arena_test.exs | 62 ++++++++++++++++++++++++++++++------ 3 files changed, 96 insertions(+), 27 deletions(-) diff --git a/lib/orb/memory.ex b/lib/orb/memory.ex index 9e72e77..a32be20 100644 --- a/lib/orb/memory.ex +++ b/lib/orb/memory.ex @@ -21,6 +21,9 @@ defmodule Orb.Memory do end end + @page_byte_size 64 * 1024 + def page_byte_size(), do: @page_byte_size + @doc """ Declare how many 64Kib pages of memory your module needs. diff --git a/test/examples/arena.exs b/test/examples/arena.exs index 32da4df..ade935a 100644 --- a/test/examples/arena.exs +++ b/test/examples/arena.exs @@ -3,19 +3,49 @@ defmodule Examples.Arena do use Orb + alias Orb.Instruction + + def alloc_impl(values_mod, byte_count) do + offset_global_name = values_mod.offset_global_name() + start_offset = values_mod.start_offset() + end_offset = values_mod.end_offset() + + Orb.snippet Orb.S32, new_ptr: I32.UnsafePointer do + new_ptr = Instruction.global_get(Orb.I32, offset_global_name) + Instruction.global_set(Orb.I32, offset_global_name, new_ptr + byte_count) + + if Instruction.global_get(Orb.I32, offset_global_name) > end_offset * Orb.Memory.page_byte_size() do + unreachable!() + end + + new_ptr + end + end + 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])) module_name = Module.concat(__MODULE__, unquote(name)) - + page_count = unquote(opts[:pages]) + page_offset = Orb.Memory.pages(page_count) offset_global_name = String.to_atom("#{Macro.inspect_atom(:literal, module_name)}.bump_offset") + Module.create( + Module.concat([__MODULE__, unquote(name), Values]), + quote do + def start_offset(), do: unquote(page_offset) + def end_offset(), do: unquote(page_offset + page_count) + def offset_global_name(), do: unquote(offset_global_name) + end, + unquote(Macro.Env.location(__CALLER__)) + ) + + global( do: [ {offset_global_name, page_offset * 64 * 1024} @@ -36,27 +66,21 @@ defmodule Examples.Arena do # end with do - Module.create(module_name, quote do - use Orb + Orb.__mode_pre(Orb.S32) - # offset_global_name = unquote(offset_global_name) + defmodule unquote(name) do + use Orb - set_func_prefix(inspect(unquote(module_name))) + alias __MODULE__.Values - # @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))) + set_func_prefix(inspect(__MODULE__)) # 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 + defw alloc(byte_count: I32), I32.UnsafePointer, new_ptr: I32.UnsafePointer do + # Examples.Arena.alloc_impl(Values, byte_count) + Examples.Arena.alloc_impl(Values, Instruction.local_get(I32, :byte_count)) end - end, unquote(Macro.Env.location(__CALLER__))) + end end # Orb.include(module_name) diff --git a/test/examples/arena_test.exs b/test/examples/arena_test.exs index 35b95ee..5e7d1c5 100644 --- a/test/examples/arena_test.exs +++ b/test/examples/arena_test.exs @@ -13,27 +13,69 @@ defmodule Examples.ArenaTest do Arena.def(First, pages: 2) Arena.def(Second, pages: 3) - defw test(), I32 do + defw test(), {I32, I32, I32} do First.alloc(16) + First.alloc(16) + Second.alloc(16) + end + + defw just_enough(), I32, i: I32, final: I32 do + loop HereWeGo do + final = First.alloc(16) + + i = i + 1 + HereWeGo.continue(if: i < 2 * 64 * 1024 / 16) + end + final + end + + defw too_many(), i: I32 do + loop HereWeGo do + _ = First.alloc(16) + + i = i + 1 + HereWeGo.continue(if: i <= 2 * 64 * 1024 / 16) + end end end test "add func prefixes" do - assert ~S""" + assert A.to_wat() =~ ~S""" (module $A (memory (export "memory") 5) (global $Examples.ArenaTest.A.First.bump_offset (mut i32) (i32.const 0)) (global $Examples.ArenaTest.A.Second.bump_offset (mut i32) (i32.const 131072)) + """ + + assert A.to_wat() =~ ~S""" (func $Examples.ArenaTest.A.First.alloc (param $byte_count i32) (result i32) - (i32.const 0) - ) - (func $Examples.ArenaTest.A.Second.alloc (param $byte_count i32) (result i32) - (i32.const 0) - ) - (func $test (export "test") (result i32) + """ + + assert A.to_wat() =~ ~S""" + (func $test (export "test") (result i32 i32 i32) + (call $Examples.ArenaTest.A.First.alloc (i32.const 16)) (call $Examples.ArenaTest.A.First.alloc (i32.const 16)) + (call $Examples.ArenaTest.A.Second.alloc (i32.const 16)) ) - ) - """ = A.to_wat() + """ + end + + test "allocates separate memory offsets" do + # IO.puts(A.to_wat()) + i = Instance.run(A) + f = Instance.capture(i, :test, 0) + assert f.() === {0, 16, 131072} + end + + test "just enough allocations" do + i = Instance.run(A) + f = Instance.capture(i, :just_enough, 0) + assert 131056 = f.() + end + + test "too many allocations" do + i = Instance.run(A) + f = Instance.capture(i, :too_many, 0) + assert {:error, _} = f.() end end