Skip to content

Commit

Permalink
Tidy up preprocessor, parser and interpreter for instruction abbrevia…
Browse files Browse the repository at this point in the history
…tions (#17)

Despite #12, the preprocessor, parser and interpreter (and their tests)
were still a little messy and inconsistent. This commit cleans them up
to fix many minor annoyances in preparation for implementing instruction
abbreviations.

The problems addressed are mostly related to naming and code
organisation. The largest change is that all of the preprocessor tests
have been updated to no longer depend upon the remaining instruction
abbreviations (i.e. folded instructions and missing `call_index` index)
which we intend to desugar.
  • Loading branch information
tomstuart committed Aug 20, 2023
2 parents 59e4f32 + af49d3b commit b19669a
Show file tree
Hide file tree
Showing 5 changed files with 263 additions and 235 deletions.
117 changes: 56 additions & 61 deletions lib/wasminna/ast_parser.rb
Expand Up @@ -184,7 +184,10 @@ def parse_invoke
read => 'invoke'
read_optional_id => module_name
parse_string => name
arguments = parse_instructions(context: Context.new)
arguments =
repeatedly do
read_list { parse_instruction(context: Context.new) }
end

Invoke.new(module_name:, name:, arguments:)
end
Expand Down Expand Up @@ -238,22 +241,22 @@ def parse_expecteds
in 'f32.const' | 'f64.const'
parse_float_expectation
else
parse_instructions(context: Context.new)
parse_instruction(context: Context.new)
end
end
end
end

def parse_float_expectation
read => 'f32.const' | 'f64.const' => opcode
bits = opcode.slice(%r{\d+}).to_i(10)
read => 'f32.const' | 'f64.const' => keyword
bits = keyword.slice(%r{\d+}).to_i(10)

if peek in 'nan:canonical' | 'nan:arithmetic'
read => 'nan:canonical' | 'nan:arithmetic' => nan
NanExpectation.new(nan:, bits:)
else
number = parse_float(bits:)
[Const.new(type: :float, bits:, number:)]
Const.new(type: :float, bits:, number:)
end
end

Expand Down Expand Up @@ -555,11 +558,12 @@ def parse_folded_instruction(context:)
end

def parse_folded_structured_instruction(context:)
read_labelled => [opcode, label]
read => keyword
read_optional_id => label
type = parse_blocktype(context:)
context = Context.new(labels: [label]) + context

case opcode
case keyword
in 'block'
body = parse_instructions(context:)
[Block.new(type:, body:)]
Expand Down Expand Up @@ -594,9 +598,9 @@ def parse_folded_plain_instruction(context:)
end

def parse_numeric_instruction
read => opcode
read => keyword

opcode.match(NUMERIC_OPCODE_REGEXP) =>
keyword.match(NUMERIC_OPCODE_REGEXP) =>
{ type:, bits:, operation: }
type = { 'f' => :float, 'i' => :integer }.fetch(type)
bits = bits.to_i(10)
Expand Down Expand Up @@ -684,27 +688,32 @@ def parse_numeric_instruction
end

def parse_structured_instruction(context:)
read_labelled => [opcode, label]
read => keyword
read_optional_id => label
type = parse_blocktype(context:)
context = Context.new(labels: [label]) + context

read_list(from: read_until('end')) do
case opcode
read_instructions(until: 'end') do
case keyword
in 'block'
body = parse_instructions(context:)
Block.new(type:, body:)
in 'loop'
body = parse_instructions(context:)
Loop.new(type:, body:)
in 'if'
consequent = parse_consequent(context:)
read_labelled('else', label:) if peek in 'else'
alternative = parse_alternative(context:)
consequent = read_instructions(until: 'else') { parse_instructions(context:) }
if peek in 'else'
read => 'else'
read_optional_id => nil | ^label
end
alternative = parse_instructions(context:)

If.new(type:, consequent:, alternative:)
end
end.tap do
read_labelled('end', label:)
read => 'end'
read_optional_id => nil | ^label
end
end

Expand All @@ -719,38 +728,9 @@ def parse_blocktype(context:)
end
end

def parse_consequent(context:)
read_list(from: read_until('else')) do
parse_instructions(context:)
end
end

def parse_alternative(context:)
parse_instructions(context:)
end

def read_labelled(atom = nil, label: nil)
if atom.nil?
read => atom
else
read => ^atom
end

read_optional_id => id
unless id.nil?
if label.nil?
id => label
else
id => ^label
end
end

[atom, label]
end

def parse_normal_instruction(context:)
case read
in 'return' | 'nop' | 'drop' | 'unreachable' | 'memory.grow' | 'memory.size' | 'memory.fill' | 'memory.copy' => opcode
in 'return' | 'nop' | 'drop' | 'unreachable' | 'memory.grow' | 'memory.size' | 'memory.fill' | 'memory.copy' => keyword
{
'return' => Return,
'nop' => Nop,
Expand All @@ -760,10 +740,10 @@ def parse_normal_instruction(context:)
'memory.size' => MemorySize,
'memory.fill' => MemoryFill,
'memory.copy' => MemoryCopy
}.fetch(opcode).new
in 'local.get' | 'local.set' | 'local.tee' | 'global.get' | 'global.set' | 'br' | 'br_if' | 'call' | 'memory.init' | 'data.drop' | 'elem.drop' => opcode
}.fetch(keyword).new
in 'local.get' | 'local.set' | 'local.tee' | 'global.get' | 'global.set' | 'br' | 'br_if' | 'call' | 'memory.init' | 'data.drop' | 'elem.drop' => keyword
index_space =
case opcode
case keyword
in 'local.get' | 'local.set' | 'local.tee'
context.locals
in 'global.get' | 'global.set'
Expand Down Expand Up @@ -791,8 +771,8 @@ def parse_normal_instruction(context:)
'memory.init' => MemoryInit,
'data.drop' => DataDrop,
'elem.drop' => ElemDrop
}.fetch(opcode).new(index:)
in 'table.get' | 'table.set' | 'table.fill' | 'table.grow' | 'table.size' => opcode
}.fetch(keyword).new(index:)
in 'table.get' | 'table.set' | 'table.fill' | 'table.grow' | 'table.size' => keyword
index =
if can_read_index?
parse_index(context.tables)
Expand All @@ -806,7 +786,7 @@ def parse_normal_instruction(context:)
'table.fill' => TableFill,
'table.grow' => TableGrow,
'table.size' => TableSize
}.fetch(opcode).new(index:)
}.fetch(keyword).new(index:)
in 'br_table'
indexes =
repeatedly do
Expand Down Expand Up @@ -866,19 +846,34 @@ def parse_normal_instruction(context:)
end
end

def read_until(terminator)
def read_instructions(**kwargs, &)
return read_list(from: read_instructions(**kwargs), &) if block_given?

kwargs => { until: terminator }

repeatedly do
case peek
in ^terminator
raise StopIteration
in 'block' | 'loop' | 'if'
[read, *read_until('end'), read]
else
[read]
end
raise StopIteration if peek in ^terminator
read_instruction
end.flatten(1)
end

def read_instruction
case peek
in 'block' | 'loop' | 'if'
read_structured_instruction
else
[read]
end
end

def read_structured_instruction
[
read,
*read_instructions(until: 'end'),
read
]
end

def unsigned(signed, bits:)
signed = mask(signed, bits:)
size = 1 << bits
Expand Down
2 changes: 2 additions & 0 deletions lib/wasminna/helpers.rb
Expand Up @@ -21,6 +21,8 @@ module ReadFromSExpression
private

def read_list(from: read, starting_with: nil)
return read_list(from:, starting_with:) { repeatedly { read } } unless block_given?

raise "not a list: #{from.inspect}" unless from in [*]
previous_s_expression, self.s_expression = self.s_expression, from

Expand Down
2 changes: 1 addition & 1 deletion lib/wasminna/interpreter.rb
Expand Up @@ -84,7 +84,7 @@ def evaluate_script(script)
float = Float.decode(actual_value, format:).to_f
success = float.nan? # TODO check whether canonical or arithmetic
else
evaluate_expression(expected, locals: [])
evaluate_instruction(expected, locals: [])
stack.pop(1) => [expected_value]
raise unless stack.empty?
success = actual_value == expected_value
Expand Down

0 comments on commit b19669a

Please sign in to comment.