Skip to content

Commit

Permalink
Fix type for memory stores
Browse files Browse the repository at this point in the history
  • Loading branch information
RoyalIcing committed Dec 6, 2023
1 parent 2d1997a commit d7452bd
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 15 deletions.
4 changes: 4 additions & 0 deletions lib/orb.ex
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,8 @@ defmodule Orb do

@before_compile unquote(__MODULE__).BeforeCompile

@orb_experimental %{}

def __wasm_body__(_), do: []
defoverridable __wasm_body__: 1

Expand All @@ -556,6 +558,8 @@ defmodule Orb do
Module.register_attribute(__MODULE__, :wasm_types, accumulate: true)
Module.register_attribute(__MODULE__, :wasm_table_allocations, accumulate: true)
Module.register_attribute(__MODULE__, :wasm_imports, accumulate: true)

# Module.register_attribute(__MODULE__, :orb_experimental, accumulate: false)
end
end

Expand Down
7 changes: 4 additions & 3 deletions lib/orb/dsl.ex
Original file line number Diff line number Diff line change
Expand Up @@ -247,10 +247,11 @@ defmodule Orb.DSL do
end

quote do:
Orb.Instruction.i32(
Orb.Instruction.memory_store(
:i32,
unquote(store_instruction),
unquote(computed_offset),
unquote(value)
offset: unquote(computed_offset),
value: unquote(value)
)

{:=, _, [{local, _, nil}, input]}
Expand Down
54 changes: 53 additions & 1 deletion lib/orb/instruction.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule Orb.Instruction do
require Orb.Ops, as: Ops
alias Orb.Constants

@types [:i64, :i32, :f64, :f32, :local_effect, :global_effect, :unknown_effect]
@types [:i64, :i32, :f64, :f32, :local_effect, :global_effect, :memory_effect, :unknown_effect]

# def new(type, operation, operands \\ [])
def new(type, operation), do: new(type, operation, [])
Expand Down Expand Up @@ -110,7 +110,26 @@ defmodule Orb.Instruction do
def global_set(type, global_name, value),
do: new(:global_effect, {:global_set, global_name, type}, [value])

@types_to_stores %{
i32: %{store: true, store8: true, store16: true},
i64: %{store: true, store8: true, store16: true, store32: true},
f32: %{store: true},
f64: %{store: true}
}

def memory_store(type, store, opts)
when is_map_key(@types_to_stores, type) and
is_map_key(:erlang.map_get(type, @types_to_stores), store) do
offset = Keyword.fetch!(opts, :offset)
value = Keyword.fetch!(opts, :value)
new(:memory_effect, {type, store}, [offset, value])
end

def memory_size(), do: new(:i32, {:memory, :size}, [])
# This is really a memory_effect but we don’t have a way to have
# both a function returning i32 and performing a memory effect.
# Which we should because any function can do both!
# TODO: add effects map. See InstructionSequence for a sketch.
def memory_grow(value), do: new(:i32, {:memory, :grow}, [value])

defp type_check_operands!(type, operation, operands) do
Expand Down Expand Up @@ -210,6 +229,19 @@ defmodule Orb.Instruction do
operand
end

defp type_check_operand!(
:memory_effect,
{:i32, :store},
operand,
1
) do
received_type = get_operand_type(operand)

types_must_match!(:i32, received_type, "i32.store")

operand
end

defp type_check_operand!(_type, _operation, operand, _index) do
operand
end
Expand Down Expand Up @@ -326,6 +358,26 @@ defmodule Orb.Instruction do

def to_wat(
%Orb.Instruction{
type: :memory_effect,
operation: {type, store},
operands: operands
},
indent
) do
[
indent,
"(",
to_string(type),
".",
to_string(store),
for(operand <- operands, do: [" ", Instructions.do_wat(operand)]),
")"
]
end

def to_wat(
%Orb.Instruction{
type: :i32,
operation: {:memory, memory_op},
operands: operands
},
Expand Down
5 changes: 3 additions & 2 deletions lib/orb/memory.ex
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ defmodule Orb.Memory do
Memory.store!(I32.U8, 0x100, ?a)
```
"""
def store!(type, address, value) do
def store!(type, offset, value) do
# TODO: add `align` arg https://webassembly.github.io/spec/core/bikeshed/#syntax-instr-memory
primitive_type = Orb.CustomType.resolve!(type)

load_instruction =
Expand All @@ -97,7 +98,7 @@ defmodule Orb.Memory do
:load -> :store
end

Orb.Instruction.new(primitive_type, store_instruction, [address, value])
Orb.Instruction.memory_store(primitive_type, store_instruction, offset: offset, value: value)
end

@doc """
Expand Down
2 changes: 1 addition & 1 deletion lib/orb/ops.ex
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ defmodule Orb.Ops do
@integer_types ~w(i64 i32)a
@float_types ~w(f64 f32)a
@primitive_types @integer_types ++ @float_types
@effects ~w(unknown_effect global_effect local_effect)a
@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 Down
29 changes: 29 additions & 0 deletions test/examples/hex_formatter.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
defmodule Examples.HexFormatter do
use Orb

Memory.pages(1)

wasm_mode(U32)

defw u32_to_hex_lower(value: I32, write_ptr: I32.U8.UnsafePointer),
i: I32,
digit: I32 do
i = 8

# for i <- 8..1//-1 do
loop Digits do
i = i - 1

digit = I32.rem_u(value, 16)
value = value / 16

if digit > 9 do
write_ptr[at!: i] = ?a + digit - 10
else
write_ptr[at!: i] = ?0 + digit
end

Digits.continue(if: i > 0)
end
end
end
38 changes: 38 additions & 0 deletions test/examples/hex_formatter_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
defmodule Examples.HexFormatterTest do
use ExUnit.Case, async: true

Code.require_file("hex_formatter.exs", __DIR__)
alias Examples.HexFormatter

alias OrbWasmtime.Instance

test "u32_to_hex_lower" do
# IO.puts(HexFormatter.to_wat())

inst = Instance.run(HexFormatter)
u32_to_hex_lower = Instance.capture(inst, :u32_to_hex_lower, 2)
read = &Instance.read_memory(inst, &1, 8)

u32_to_hex_lower.(0x00000000, 0x100)
assert read.(0x100) == "00000000"

u32_to_hex_lower.(255, 0x100)
assert read.(0x100) == "000000ff"

u32_to_hex_lower.(0x00ABCDEF, 0x100)
assert read.(0x100) == "00abcdef"

u32_to_hex_lower.(0x44ABCDEF, 0x100)
assert read.(0x100) == "44abcdef"

u32_to_hex_lower.(0xFFFF_FFFF, 0x100)
assert read.(0x100) == "ffffffff"

u32_to_hex_lower.(1, 0x100)
assert read.(0x100) == "00000001"

# Does NOT write outside its bounds.
assert Instance.read_memory(inst, 0x100 - 1, 1) == <<0>>
assert Instance.read_memory(inst, 0x100 + 9, 1) == <<0>>
end
end
18 changes: 10 additions & 8 deletions test/orb_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,8 @@ defmodule OrbTest do
defmodule RangeBounded do
use Orb

@orb_experimental %{range_params: true}

# defw two_to_five(a: 2.0 < F32 < 5.0), I32 do
# defw two_to_five(a: 2 <= I32 <= 5), I32 do

Expand All @@ -787,14 +789,14 @@ defmodule OrbTest do
assert wat =~ "(i32.le_s (local.get $a) (i32.const 5))"

alias OrbWasmtime.Wasm
assert 1 = Wasm.call(RangeBounded, :declare_defcon, 1)
assert 2 = Wasm.call(RangeBounded, :declare_defcon, 2)
assert 3 = Wasm.call(RangeBounded, :declare_defcon, 3)
assert 4 = Wasm.call(RangeBounded, :declare_defcon, 4)
assert 5 = Wasm.call(RangeBounded, :declare_defcon, 5)
assert {:error, _} = Wasm.call(RangeBounded, :declare_defcon, 0)
assert {:error, _} = Wasm.call(RangeBounded, :declare_defcon, 6)
assert {:error, _} = Wasm.call(RangeBounded, :declare_defcon, 10)
assert 1 = Wasm.call(wat, :declare_defcon, 1)
assert 2 = Wasm.call(wat, :declare_defcon, 2)
assert 3 = Wasm.call(wat, :declare_defcon, 3)
assert 4 = Wasm.call(wat, :declare_defcon, 4)
assert 5 = Wasm.call(wat, :declare_defcon, 5)
assert {:error, _} = Wasm.call(wat, :declare_defcon, 0)
assert {:error, _} = Wasm.call(wat, :declare_defcon, 6)
assert {:error, _} = Wasm.call(wat, :declare_defcon, 10)
end

test "loop" do
Expand Down

0 comments on commit d7452bd

Please sign in to comment.