Skip to content

Commit

Permalink
Added loop and branching execution
Browse files Browse the repository at this point in the history
  • Loading branch information
BrendoCosta committed Oct 14, 2024
1 parent 819e9a4 commit 0f69fd2
Show file tree
Hide file tree
Showing 10 changed files with 555 additions and 156 deletions.
421 changes: 282 additions & 139 deletions src/gwr/execution/machine.gleam

Large diffs are not rendered by default.

139 changes: 133 additions & 6 deletions src/gwr/execution/stack.gleam
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import gleam/option
import gleam/dict.{type Dict}
import gleam/iterator
import gleam/list
import gleam/option
import gleam/result

import gwr/execution/runtime
import gwr/syntax/instruction
Expand Down Expand Up @@ -41,7 +43,7 @@ pub type ActivationFrame
/// https://webassembly.github.io/spec/core/exec/runtime.html#activation-frames
pub type FrameState
{
FrameState(locals: List(runtime.Value), module_instance: runtime.ModuleInstance)
FrameState(locals: Dict(Int, runtime.Value), module_instance: runtime.ModuleInstance)
}

pub fn create() -> Stack
Expand Down Expand Up @@ -71,8 +73,133 @@ pub fn pop(from stack: Stack) -> #(Stack, option.Option(StackEntry))

pub fn pop_repeat(from stack: Stack, up_to count: Int)
{
iterator.fold(from: #(stack, []), over: iterator.range(1, count), with: fn (state, _) {
let #(stack, results) = pop(state.0)
#(stack, list.append(state.1, [results]))
})
case count > 0
{
False -> #(stack, [])
True ->
{
iterator.fold(
from: #(stack, []),
over: iterator.range(1, count),
with: fn (accumulator, _)
{
let #(stack_after_pop, maybe_popped_entry) = pop(accumulator.0)
case maybe_popped_entry
{
option.Some(entry) -> #(stack_after_pop, accumulator.1 |> list.append([entry]))
option.None -> #(stack_after_pop, accumulator.1)
}
}
)
}
}
}

pub fn pop_all(from stack: Stack) -> #(Stack, List(StackEntry))
{
#(create(), stack.entries |> list.reverse)
}

pub fn pop_while(from stack: Stack, with predicate: fn (StackEntry) -> Bool)
{
do_pop_while(#(stack, []), predicate)
}

fn do_pop_while(accumulator: #(Stack, List(StackEntry)), predicate: fn (StackEntry) -> Bool) -> #(Stack, List(StackEntry))
{
case peek(from: accumulator.0)
{
option.Some(entry) ->
{
case predicate(entry)
{
True -> do_pop_while(#(pop(accumulator.0).0, accumulator.1 |> list.append([entry])), predicate)
False -> #(accumulator.0, accumulator.1)
}
}
_ -> #(accumulator.0, accumulator.1)
}
}

pub fn pop_if(from stack: Stack, with predicate: fn (StackEntry) -> Bool) -> Result(#(Stack, StackEntry), Nil)
{
case pop(from: stack)
{
#(stack, option.Some(entry)) ->
{
case predicate(entry)
{
True -> Ok(#(stack, entry))
False -> Error(Nil)
}
}
_ -> Error(Nil)
}
}

pub fn pop_as(from stack: Stack, with convert: fn (StackEntry) -> Result(a, String)) -> Result(#(Stack, a), String)
{
case pop(from: stack)
{
#(stack, option.Some(entry)) ->
{
use value <- result.try(convert(entry))
Ok(#(stack, value))
}
_ -> Error("gwr/execution/stack.pop_as: the stack is empty")
}
}

pub fn is_value(entry: StackEntry) -> Bool
{
case entry
{
ValueEntry(_) -> True
_ -> False
}
}

pub fn is_label(entry: StackEntry) -> Bool
{
case entry
{
LabelEntry(_) -> True
_ -> False
}
}

pub fn is_activation_frame(entry: StackEntry) -> Bool
{
case entry
{
ActivationEntry(_) -> True
_ -> False
}
}

pub fn to_value(entry: StackEntry) -> Result(runtime.Value, String)
{
case entry
{
ValueEntry(value) -> Ok(value)
_ -> Error("gwr/execution/stack.to_value: the entry at the top of the stack is not a ValueEntry")
}
}

pub fn to_label(entry: StackEntry) -> Result(Label, String)
{
case entry
{
LabelEntry(label) -> Ok(label)
_ -> Error("gwr/execution/stack.to_label: the entry at the top of the stack is not a LabelEntry")
}
}

pub fn to_activation_frame(entry: StackEntry) -> Result(ActivationFrame, String)
{
case entry
{
ActivationEntry(activation_frame) -> Ok(activation_frame)
_ -> Error("gwr/execution/stack.to_activation_frame: the entry at the top of the stack is not a ActivationEntry")
}
}
15 changes: 15 additions & 0 deletions src/gwr/parser/instruction_parser.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,28 @@ pub fn parse_instruction(from reader: byte_reader.ByteReader) -> Result(#(byte_r
use #(reader, expression) <- result.try(parse_expression(from: reader))
Ok(#(reader, instruction.Else(instructions: expression)))
}
0xc ->
{
use #(reader, label_index) <- result.try(value_parser.parse_unsigned_leb128_integer(from: reader))
Ok(#(reader, instruction.Br(index: label_index)))
}
0xd ->
{
use #(reader, label_index) <- result.try(value_parser.parse_unsigned_leb128_integer(from: reader))
Ok(#(reader, instruction.BrIf(index: label_index)))
}
// Variable Instructions
// https://webassembly.github.io/spec/core/binary/instructions.html#variable-instructions
0x20 ->
{
use #(reader, local_index) <- result.try(value_parser.parse_unsigned_leb128_integer(from: reader))
Ok(#(reader, instruction.LocalGet(index: local_index)))
}
0x21 ->
{
use #(reader, local_index) <- result.try(value_parser.parse_unsigned_leb128_integer(from: reader))
Ok(#(reader, instruction.LocalSet(index: local_index)))
}
// Numeric Instructions
// https://webassembly.github.io/spec/core/binary/instructions.html#numeric-instructions
0x41 ->
Expand Down
3 changes: 2 additions & 1 deletion src/gwr/syntax/instruction.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ pub type Instruction
If(block_type: BlockType, instructions: List(Instruction), else_: Option(Instruction))
Else(instructions: Expression)
Br(index: index.LabelIndex)
BrIf(label: index.LabelIndex)
BrIf(index: index.LabelIndex)
BrTable(label: index.LabelIndex)
Return
Call(function: index.FunctionIndex)
CallIndirect(table: index.TableIndex, type_: index.TypeIndex)
/// https://webassembly.github.io/spec/core/binary/instructions.html#variable-instructions
LocalGet(index: index.LocalIndex)
LocalSet(index: index.LocalIndex)

/// https://webassembly.github.io/spec/core/binary/instructions.html#numeric-instructions

Expand Down
Binary file added test/assets/control/loop.wasm
Binary file not shown.
20 changes: 20 additions & 0 deletions test/assets/control/loop.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
;; wat2wasm ./test/assets/control/loop.wat -o ./test/assets/control/loop.wasm
(module
(func $loop_test (export "loop_test") (result i32)
(local $i i32)
(loop $test_loop
local.get $i
i32.const 1
i32.add

local.set $i

local.get $i
i32.const 10
i32.lt_s

br_if $test_loop
)
local.get $i
)
)
8 changes: 8 additions & 0 deletions test/gwr/execution/execution_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,11 @@ pub fn if_else___else_scope___test()
let #(_, result) = gwr.call(instance, "if_else_test", [runtime.Integer32(1)]) |> should.be_ok
result |> should.equal([runtime.Integer32(120)])
}

pub fn loop___test()
{
let module_data = simplifile.read_bits(from: "./test/assets/control/loop.wasm") |> should.be_ok
let instance = gwr.create(from: module_data) |> should.be_ok
let #(_, result) = gwr.call(instance, "loop_test", []) |> should.be_ok
result |> should.equal([runtime.Integer32(10)])
}
43 changes: 41 additions & 2 deletions test/gwr/execution/machine_test.gleam
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import gleam/dict
import gleam/int
import gleam/io
import gleam/list
import gleam/string
Expand Down Expand Up @@ -36,7 +38,7 @@ fn create_empty_state() -> machine.MachineState
(
framestate: stack.FrameState
(
locals: [],
locals: dict.new(),
module_instance: runtime.ModuleInstance
(
data_addresses: [],
Expand Down Expand Up @@ -1119,7 +1121,7 @@ pub fn local_get_test()
framestate: stack.FrameState
(
..state.configuration.thread.framestate,
locals: [runtime.Integer32(2), runtime.Integer32(256), runtime.Integer32(512)] // 0, 1, 2
locals: dict.from_list([#(0, runtime.Integer32(2)), #(1, runtime.Integer32(256)), #(2, runtime.Integer32(512))])
)
)
)
Expand All @@ -1131,3 +1133,40 @@ pub fn local_get_test()
|> should.be_some
|> should.equal(stack.ValueEntry(runtime.Integer32(256)))
}

pub fn local_set_test()
{
let state = create_empty_state()
let state = machine.MachineState
(
configuration: machine.Configuration
(
..state.configuration,
thread: machine.Thread
(
..state.configuration.thread,
framestate: stack.FrameState
(
..state.configuration.thread.framestate,
locals: dict.from_list([
#(0, runtime.Integer32(2)),
#(1, runtime.Integer32(256)),
#(2, runtime.Integer32(512))
])
)
)
),
stack: stack.create() |> stack.push([stack.ValueEntry(runtime.Integer32(1024))])
)

let state = machine.local_set(state, 2) |> should.be_ok

state.configuration.thread.framestate.locals
|> dict.to_list
|> list.sort(by: fn (a, b) { int.compare(a.0, b.0) }) // Order by the local's index
|> should.equal([
#(0, runtime.Integer32(2)),
#(1, runtime.Integer32(256)),
#(2, runtime.Integer32(1024))
])
}
36 changes: 28 additions & 8 deletions test/gwr/execution/stack_test.gleam
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import gleam/option

import gwr/execution/runtime
import gwr/execution/stack

Expand Down Expand Up @@ -32,9 +30,11 @@ pub fn push___once___test()
pub fn push___multiple___test()
{
let stack = stack.create()
|> stack.push([stack.ValueEntry(runtime.Integer32(1))])
|> stack.push([stack.ValueEntry(runtime.Integer32(2))])
|> stack.push([stack.ValueEntry(runtime.Integer32(3))])
|> stack.push([
stack.ValueEntry(runtime.Integer32(1)),
stack.ValueEntry(runtime.Integer32(2)),
stack.ValueEntry(runtime.Integer32(3))
])

stack.peek(stack)
|> should.be_some
Expand Down Expand Up @@ -91,11 +91,31 @@ pub fn pop_repeat_test()
let #(stack, result) = stack.pop_repeat(stack, 3)
result
|> should.equal([
option.Some(stack.ValueEntry(runtime.Integer32(3))),
option.Some(stack.ValueEntry(runtime.Integer32(2))),
option.Some(stack.ValueEntry(runtime.Integer32(1)))
stack.ValueEntry(runtime.Integer32(3)),
stack.ValueEntry(runtime.Integer32(2)),
stack.ValueEntry(runtime.Integer32(1))
])

stack.peek(stack)
|> should.be_none
}

pub fn pop_while_test()
{
let stack = stack.create()
|> stack.push([stack.ValueEntry(runtime.Integer32(1))])
|> stack.push([stack.LabelEntry(stack.Label(arity: 0, continuation: []))])
|> stack.push([stack.ValueEntry(runtime.Integer32(2))])
|> stack.push([stack.ValueEntry(runtime.Integer32(3))])

let #(stack, result) = stack.pop_while(stack, stack.is_value)
result
|> should.equal([
stack.ValueEntry(runtime.Integer32(3)),
stack.ValueEntry(runtime.Integer32(2))
])

stack.peek(stack)
|> should.be_some
|> should.equal(stack.LabelEntry(stack.Label(arity: 0, continuation: [])))
}
26 changes: 26 additions & 0 deletions test/gwr/parser/instruction_parser_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -161,4 +161,30 @@ pub fn parse_instruction___local_get___test()
instruction.LocalGet(index: 255)
)
)
}

pub fn parse_instruction___br___test()
{
let reader = byte_reader.create(from: <<0x0c, 0xff, 0x01>>)
instruction_parser.parse_instruction(reader)
|> should.be_ok
|> should.equal(
#(
byte_reader.ByteReader(..reader, current_position: 3),
instruction.Br(index: 255)
)
)
}

pub fn parse_instruction___br_if___test()
{
let reader = byte_reader.create(from: <<0x0d, 0x80, 0x80, 0x04>>)
instruction_parser.parse_instruction(reader)
|> should.be_ok
|> should.equal(
#(
byte_reader.ByteReader(..reader, current_position: 4),
instruction.BrIf(index: 65536)
)
)
}

0 comments on commit 0f69fd2

Please sign in to comment.