Support structs in extracting and inputting values

This commit is contained in:
Johan B.W. de Vries 2023-11-14 15:04:20 +01:00
parent a73a3b2bb4
commit bd80210ba3
9 changed files with 221 additions and 7 deletions

View File

@ -51,3 +51,8 @@ clean-generated-tests:
.SECONDARY: # Keep intermediate files
.PHONY: examples
# So generally the right thing to do is to delete the target file if the recipe fails after beginning to change the file.
# make will do this if .DELETE_ON_ERROR appears as a target.
# This is almost always what you want make to do, but it is not historical practice; so for compatibility, you must explicitly request it.
.DELETE_ON_ERROR:

View File

@ -1,7 +1,7 @@
"""
This module contains the code to convert parsed Ourlang into WebAssembly code
"""
from typing import List, Union
from typing import List, Optional, Union
import struct
@ -59,6 +59,11 @@ def type3(inp: type3types.Type3OrPlaceholder) -> wasm.WasmType:
if inp == type3types.u64:
return wasm.WasmTypeInt64()
if inp == type3types.i8:
# WebAssembly has only support for 32 and 64 bits
# So we need to store more memory per byte
return wasm.WasmTypeInt32()
if inp == type3types.i32:
return wasm.WasmTypeInt32()
@ -853,10 +858,13 @@ def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstruc
# Store each member individually
for memname, mtyp3 in inp.struct_type3.members.items():
mtyp: Optional[str]
if isinstance(mtyp3, type3types.StructType3):
mtyp = 'i32'
else:
mtyp = LOAD_STORE_TYPE_MAP.get(mtyp3.name)
if mtyp is None:
# In the future might extend this by having structs or tuples
# as members of struct or tuples
raise NotImplementedError(expression, inp, mtyp3)
wgn.local.get(tmp_var)

View File

@ -24,7 +24,7 @@ def calculate_alloc_size(typ: type3types.Type3, is_member: bool = False) -> int:
return 4
return sum(
calculate_alloc_size(x)
calculate_alloc_size(x, is_member=True)
for x in typ.members.values()
)

View File

@ -102,6 +102,11 @@ class Suite:
wasm_args.append(adr)
continue
if isinstance(arg_typ, type3types.StructType3):
adr = _allocate_memory_stored_value(runner, arg_typ, arg)
wasm_args.append(adr)
continue
raise NotImplementedError(arg)
write_header(sys.stderr, 'Memory (pre run)')
@ -157,6 +162,11 @@ def _write_memory_stored_value(
runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2))
return 4
if isinstance(val_typ, type3types.StructType3):
adr2 = _allocate_memory_stored_value(runner, val_typ, val)
runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2))
return 4
raise NotImplementedError(val_typ, val)
def _allocate_memory_stored_value(
@ -214,6 +224,24 @@ def _allocate_memory_stored_value(
offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val)
return adr
if isinstance(val_typ, type3types.StructType3):
assert isinstance(val, dict)
alloc_size = calculate_alloc_size(val_typ)
adr = runner.call('stdlib.alloc.__alloc__', alloc_size)
assert isinstance(adr, int)
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
assert list(val.keys()) == list(val_typ.members.keys())
offset = adr
for val_el_name, val_el_typ in val_typ.members.items():
assert not isinstance(val_el_typ, type3types.PlaceholderForType)
val_el_val = val[val_el_name]
offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val)
return adr
raise NotImplementedError(val_typ, val)
def _load_memory_stored_returned_value(
@ -267,6 +295,9 @@ def _load_memory_stored_returned_value(
return _load_tuple_from_address(runner, ret_type3, wasm_value)
if isinstance(ret_type3, type3types.StructType3):
return _load_struct_from_address(runner, ret_type3, wasm_value)
raise NotImplementedError(ret_type3, wasm_value)
def _unpack(runner: runners.RunnerBase, typ: type3types.Type3, inp: bytes) -> Any:
@ -305,7 +336,7 @@ def _unpack(runner: runners.RunnerBase, typ: type3types.Type3, inp: bytes) -> An
return struct.unpack('<d', inp)[0]
if typ is type3types.bytes:
# Note: For applied types, inp should contain a 4 byte pointer
# Note: For bytes, inp should contain a 4 byte pointer
assert len(inp) == 4
adr = struct.unpack('<I', inp)[0]
@ -322,6 +353,13 @@ def _unpack(runner: runners.RunnerBase, typ: type3types.Type3, inp: bytes) -> An
if typ.base is type3types.tuple:
return _load_tuple_from_address(runner, typ, adr)
if isinstance(typ, type3types.StructType3):
# Note: For structs, inp should contain a 4 byte pointer
assert len(inp) == 4
adr = struct.unpack('<I', inp)[0]
return _load_struct_from_address(runner, typ, adr)
raise NotImplementedError(typ, inp)
def _load_bytes_from_address(runner: runners.RunnerBase, typ: type3types.Type3, adr: int) -> bytes:
@ -383,3 +421,29 @@ def _load_tuple_from_address(runner: runners.RunnerBase, typ: type3types.Type3,
_unpack(runner, arg_typ, arg_bytes)
for arg_typ, arg_bytes in zip(typ_list, _split_read_bytes(read_bytes, arg_sizes))
)
def _load_struct_from_address(runner: runners.RunnerBase, typ: type3types.Type3, adr: int) -> Any:
sys.stderr.write(f'Reading 0x{adr:08x} {typ:s}\n')
assert isinstance(typ, type3types.StructType3)
name_list = list(typ.members)
typ_list = [
x
for x in typ.members.values()
if not isinstance(x, type3types.PlaceholderForType)
]
assert len(typ_list) == len(typ.members)
arg_sizes = [
calculate_alloc_size(x, is_member=True)
for x in typ_list
]
read_bytes = runner.interpreter_read_memory(adr, sum(arg_sizes))
return {
arg_name: _unpack(runner, arg_typ, arg_bytes)
for arg_name, arg_typ, arg_bytes in zip(name_list, typ_list, _split_read_bytes(read_bytes, arg_sizes))
}

View File

@ -118,6 +118,11 @@ if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
'Mismatch between applied types argument count',
'The type of the value returned from function constant should match its return type',
)
elif TYPE_NAME.startswith('struct_'):
expect_type_error(
TYPE + ' must be tuple (u32) instead',
'The type of the value returned from function constant should match its return type',
)
else:
expect_type_error(
'Must be tuple',
@ -175,6 +180,11 @@ if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
'Mismatch between applied types argument count',
'The type of the value returned from function constant should match its return type',
)
elif TYPE_NAME.startswith('struct_'):
expect_type_error(
TYPE + ' must be tuple (u32) instead',
'The type of the value returned from function constant should match its return type',
)
else:
expect_type_error(
TYPE_NAME + ' must be tuple (u32) instead',
@ -226,6 +236,11 @@ if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
'Mismatch between applied types argument count',
'The type of the value returned from function select should match its return type',
)
elif TYPE_NAME.startswith('struct_'):
expect_type_error(
TYPE + ' must be tuple (u32) instead',
'The type of the value returned from function select should match its return type',
)
else:
expect_type_error(
TYPE_NAME + ' must be tuple (u32) instead',
@ -274,6 +289,11 @@ if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
# FIXME: Shouldn't this be the same as for the else statement?
'The type of the value passed to argument x of function helper should match the type of that argument',
)
elif TYPE_NAME.startswith('struct_'):
expect_type_error(
TYPE + ' must be tuple (u32) instead',
'The type of the value passed to argument x of function helper should match the type of that argument',
)
else:
expect_type_error(
'Must be tuple',
@ -325,6 +345,11 @@ if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
'Mismatch between applied types argument count',
'The type of the value passed to argument x of function helper should match the type of that argument',
)
elif TYPE_NAME.startswith('struct_'):
expect_type_error(
TYPE + ' must be tuple (u32) instead',
'The type of the value passed to argument x of function helper should match the type of that argument',
)
else:
expect_type_error(
TYPE_NAME + ' must be tuple (u32) instead',

View File

@ -1,3 +1,5 @@
from typing import Any, Dict
import functools
import json
import sys
@ -27,6 +29,9 @@ def get_tests(template):
def apply_settings(settings, txt):
for k, v in settings.items():
if k in ('CODE_HEADER', 'PYTHON'):
continue
txt = txt.replace(f'${k}', v)
return txt
@ -42,16 +47,41 @@ def generate_assertion_expect_type_error(result, error_msg, error_comment = None
result.append(f'assert {repr(error_msg)} == exc_info.value.args[0][0].msg')
result.append(f'assert {repr(error_comment)} == exc_info.value.args[0][0].comment')
def json_does_not_support_byte_values_fix(inp: Dict[Any, Any]):
key_names = list(inp)
for key in key_names:
value = inp[key]
if isinstance(value, str):
if value.startswith('bytes:'):
inp[key] = value[6:].encode()
continue
if isinstance(value, dict):
json_does_not_support_byte_values_fix(value)
continue
if isinstance(value, list):
raise NotImplementedError
def generate_assertions(settings, result_code):
result = []
locals_ = {
'VAL0': eval(settings['VAL0']),
'TYPE': settings['TYPE'],
'TYPE_NAME': settings['TYPE_NAME'],
'expect': functools.partial(generate_assertion_expect, result),
'expect_type_error': functools.partial(generate_assertion_expect_type_error, result),
}
if 'PYTHON' in settings:
locals_.update(settings['PYTHON'])
if 'VAL0' not in locals_:
locals_['VAL0'] = eval(settings['VAL0'])
json_does_not_support_byte_values_fix(locals_)
exec(result_code, {}, locals_)
return ' ' + '\n '.join(result) + '\n'
@ -93,9 +123,14 @@ def generate_code(markdown, template, settings):
print(' ' + user_story.replace('\n', '\n '))
print(' """')
print(' code_py = """')
if 'CODE_HEADER' in settings:
for lin in settings['CODE_HEADER']:
print(lin)
print()
print(inp_code.rstrip('\n'))
print('"""')
print()
print(generate_assertions(settings, result_code))
print()

View File

@ -0,0 +1,40 @@
{
"TYPE_NAME": "struct_all_primitives",
"TYPE": "StructallPrimitives",
"VAL0": "StructallPrimitives(1, 4, 8, 1, -1, 4, -4, 8, -8, 125.125, -125.125, 5000.5, -5000.5, b'Hello, world!')",
"CODE_HEADER": [
"class StructallPrimitives:",
" val00: u8",
" val01: u32",
" val02: u64",
" val10: i8",
" val11: i8",
" val12: i32",
" val13: i32",
" val14: i64",
" val15: i64",
" val20: f32",
" val21: f32",
" val22: f64",
" val23: f64",
" val30: bytes"
],
"PYTHON": {
"VAL0": {
"val00": 1,
"val01": 4,
"val02": 8,
"val10": 1,
"val11": -1,
"val12": 4,
"val13": -4,
"val14": 8,
"val15": -8,
"val20": 125.125,
"val21": -125.125,
"val22": 5000.5,
"val23": -5000.5,
"val30": "bytes:Hello, world!"
}
}
}

View File

@ -0,0 +1,25 @@
{
"TYPE_NAME": "struct_nested",
"TYPE": "StructNested",
"VAL0": "StructNested(4, SubStruct(8, 16), 20)",
"CODE_HEADER": [
"class SubStruct:",
" val00: u8",
" val01: u8",
"",
"class StructNested:",
" val00: u64",
" val01: SubStruct",
" val02: u64"
],
"PYTHON": {
"VAL0": {
"val00": 4,
"val01": {
"val00": 8,
"val01": 16
},
"val02": 20
}
}
}

View File

@ -0,0 +1,12 @@
{
"TYPE_NAME": "struct_one_field",
"TYPE": "StructOneField",
"VAL0": "StructOneField(4)",
"CODE_HEADER": [
"class StructOneField:",
" value: u32"
],
"PYTHON": {
"VAL0": {"value": 4}
}
}