Skip to content

Commit

Permalink
Allow type definitions to be provided after preprocessing a module (#14)
Browse files Browse the repository at this point in the history
To desugar the type use abbreviation [0] we’ll need access to a list of
all the module’s type definitions [1] when preprocessing a type use [2],
but the full list of type definitions will only be available once the
complete module has been processed. This creates a chicken-and-egg
situation: we can’t desugar a type use until we’ve processed all of the
enclosing module’s fields, but many of those fields will contain type
uses which need to be desugared during processing.

This commit breaks the deadlock by refactoring the preprocessor to allow
some field-processing methods to return a “deferred result”, namely a
proc which must be called with a list of type definitions in order to
produce the final S-expression. Deferred results give us the ability to
process a module’s fields first and then provide type definitions later;
the next step will be to generate the type definitions during processing
so we can make use of that ability.

This continues the preparatory work from #12 and #13. We’re still in the
“make the change easy” [3] phase, so there’s no externally visible
difference in the preprocessor’s behaviour yet.

[0] https://webassembly.github.io/spec/core/text/modules.html#abbreviations
[1] https://webassembly.github.io/spec/core/text/modules.html#types
[2] https://webassembly.github.io/spec/core/text/modules.html#type-uses
[3] https://twitter.com/KentBeck/status/250733358307500032
  • Loading branch information
tomstuart committed Jul 19, 2023
2 parents d434813 + c549640 commit 046ad84
Showing 1 changed file with 126 additions and 52 deletions.
178 changes: 126 additions & 52 deletions lib/wasminna/preprocessor.rb
Expand Up @@ -51,6 +51,8 @@ def process_command
end
end

DUMMY_TYPE_DEFINITIONS = []

def process_module
read => 'module'
read_optional_id => id
Expand All @@ -60,15 +62,19 @@ def process_module
strings = repeatedly { read }
['module', *id, 'binary', *strings]
else
fields = process_fields
fields = process_fields.call(DUMMY_TYPE_DEFINITIONS)
['module', *id, *fields]
end
end

def process_fields
repeatedly do
read_list { process_field }
end.flatten(1)
end.then do |fields|
after_all_fields do |type_definitions|
fields.flat_map { |field| field.call(type_definitions) }
end
end
end

def process_field
Expand Down Expand Up @@ -105,9 +111,11 @@ def process_function_definition
locals = process_locals
body = process_instructions

[
['func', *id, *typeuse, *locals, *body]
]
after_all_fields do |type_definitions|
[
['func', *id, *typeuse.call(type_definitions), *locals, *body.call(type_definitions)]
]
end
end
end

Expand All @@ -122,9 +130,11 @@ def process_table_definition
else
rest = repeatedly { read }

[
['table', *id, *rest]
]
after_all_fields do
[
['table', *id, *rest]
]
end
end
end

Expand Down Expand Up @@ -170,9 +180,11 @@ def process_memory_definition
else
rest = repeatedly { read }

[
['memory', *id, *rest]
]
after_all_fields do
[
['memory', *id, *rest]
]
end
end
end

Expand Down Expand Up @@ -207,9 +219,11 @@ def process_global_definition
read => type
instructions = process_instructions

[
['global', *id, type, *instructions]
]
after_all_fields do |type_definitions|
[
['global', *id, type, *instructions.call(type_definitions)]
]
end
end
end

Expand Down Expand Up @@ -261,7 +275,9 @@ def process_typeuse
parameters = process_parameters
results = process_results

[*type, *parameters, *results]
after_all_fields do
[*type, *parameters, *results]
end
end

def process_parameters
Expand Down Expand Up @@ -302,26 +318,44 @@ def process_instructions
read_optional_id => id
blocktype = process_blocktype

[kind, *id, *blocktype]
after_all_fields do |type_definitions|
[kind, *id, *blocktype.call(type_definitions)]
end
in 'call_indirect'
read => 'call_indirect'
if can_read_index?
read_index => index
end
typeuse = process_typeuse

['call_indirect', *index, *typeuse]
after_all_fields do |type_definitions|
['call_indirect', *index, *typeuse.call(type_definitions)]
end
in 'select'
read => 'select'
results = process_results

['select', *results]
after_all_fields do
['select', *results]
end
in [*]
read_list { [process_instructions] }
read_list { process_instructions }.then do |result|
after_all_fields do |type_definitions|
[result.call(type_definitions)]
end
end
else
[read]
read.then do |result|
after_all_fields do
[result]
end
end
end
end.flatten(1)
end.then do |results|
after_all_fields do |type_definitions|
results.flat_map { |result| result.call(type_definitions) }
end
end
end

def process_blocktype
Expand All @@ -335,7 +369,9 @@ def process_blocktype

case blocktype
in [] | [['result', _]]
blocktype
after_all_fields do
blocktype
end
else
read_list(from: blocktype) { process_typeuse }
end
Expand All @@ -350,9 +386,11 @@ def process_type_definition
read_optional_id => id
functype = read_list { process_functype }

[
['type', *id, functype]
]
after_all_fields do
[
['type', *id, functype]
]
end
end

def process_functype
Expand All @@ -369,9 +407,11 @@ def process_import
read => name
descriptor = read_list { process_import_descriptor }

[
['import', module_name, name, descriptor]
]
after_all_fields do |type_definitions|
[
['import', module_name, name, descriptor.call(type_definitions)]
]
end
end

def process_import_descriptor
Expand All @@ -381,9 +421,15 @@ def process_import_descriptor
read_optional_id => id
typeuse = process_typeuse

['func', *id, *typeuse]
after_all_fields do |type_definitions|
['func', *id, *typeuse.call(type_definitions)]
end
else
repeatedly { read }
repeatedly { read }.then do |result|
after_all_fields do
result
end
end
end
end

Expand Down Expand Up @@ -412,26 +458,32 @@ def process_active_element_segment(id:)
table_use = %w[table 0]
end

[
['elem', *id, table_use, offset, *element_list]
]
after_all_fields do |type_definitions|
[
['elem', *id, table_use, offset.call(type_definitions), *element_list.call(type_definitions)]
]
end
end

def process_declarative_element_segment(id:)
read => 'declare'
element_list = process_element_list(func_optional: false)

[
['elem', *id, 'declare', *element_list]
]
after_all_fields do |type_definitions|
[
['elem', *id, 'declare', *element_list.call(type_definitions)]
]
end
end

def process_passive_element_segment(id:)
element_list = process_element_list(func_optional: false)

[
['elem', *id, *element_list]
]
after_all_fields do |type_definitions|
[
['elem', *id, *element_list.call(type_definitions)]
]
end
end

def process_offset
Expand All @@ -443,15 +495,19 @@ def process_offset
process_instruction
end

['offset', *instructions]
after_all_fields do |type_definitions|
['offset', *instructions.call(type_definitions)]
end
end

def process_element_list(func_optional:)
if peek in 'funcref' | 'externref'
read => 'funcref' | 'externref' => reftype
items = process_element_expressions

[reftype, *items]
after_all_fields do |type_definitions|
[reftype, *items.call(type_definitions)]
end
else
if !func_optional || (peek in 'func')
read => 'func'
Expand All @@ -461,13 +517,19 @@ def process_element_list(func_optional:)
process_element_expressions
end

['funcref', *items]
after_all_fields do |type_definitions|
['funcref', *items.call(type_definitions)]
end
end
end

def process_element_expressions
repeatedly do
read_list { process_element_expression }
end.then do |results|
after_all_fields do |type_definitions|
results.map { |result| result.call(type_definitions) }
end
end
end

Expand All @@ -480,7 +542,9 @@ def process_element_expression
process_instruction
end

['item', *instructions]
after_all_fields do |type_definitions|
['item', *instructions.call(type_definitions)]
end
end

def process_function_indexes
Expand Down Expand Up @@ -510,26 +574,32 @@ def process_active_data_segment(id:)
offset = read_list { process_offset }
strings = repeatedly { read }

[
['data', *id, memory_use, offset, *strings]
]
after_all_fields do |type_definitions|
[
['data', *id, memory_use, offset.call(type_definitions), *strings]
]
end
end

def process_passive_data_segment(id:)
rest = repeatedly { read }

[
['data', *id, *rest]
]
after_all_fields do
[
['data', *id, *rest]
]
end
end

def process_unabbreviated_field
read => 'export' | 'start' => kind
rest = repeatedly { read }

[
[kind, *rest]
]
after_all_fields do
[
[kind, *rest]
]
end
end

def process_assert_trap
Expand All @@ -554,5 +624,9 @@ def fresh_id
@fresh_id_index += 1
end
end

def after_all_fields
-> type_definitions { yield type_definitions }
end
end
end

0 comments on commit 046ad84

Please sign in to comment.