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 .SECONDARY: # Keep intermediate files
.PHONY: examples .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 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 import struct
@ -59,6 +59,11 @@ def type3(inp: type3types.Type3OrPlaceholder) -> wasm.WasmType:
if inp == type3types.u64: if inp == type3types.u64:
return wasm.WasmTypeInt64() 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: if inp == type3types.i32:
return wasm.WasmTypeInt32() return wasm.WasmTypeInt32()
@ -853,10 +858,13 @@ def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstruc
# Store each member individually # Store each member individually
for memname, mtyp3 in inp.struct_type3.members.items(): for memname, mtyp3 in inp.struct_type3.members.items():
mtyp = LOAD_STORE_TYPE_MAP.get(mtyp3.name) mtyp: Optional[str]
if isinstance(mtyp3, type3types.StructType3):
mtyp = 'i32'
else:
mtyp = LOAD_STORE_TYPE_MAP.get(mtyp3.name)
if mtyp is None: 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) raise NotImplementedError(expression, inp, mtyp3)
wgn.local.get(tmp_var) 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 4
return sum( return sum(
calculate_alloc_size(x) calculate_alloc_size(x, is_member=True)
for x in typ.members.values() for x in typ.members.values()
) )

View File

@ -102,6 +102,11 @@ class Suite:
wasm_args.append(adr) wasm_args.append(adr)
continue continue
if isinstance(arg_typ, type3types.StructType3):
adr = _allocate_memory_stored_value(runner, arg_typ, arg)
wasm_args.append(adr)
continue
raise NotImplementedError(arg) raise NotImplementedError(arg)
write_header(sys.stderr, 'Memory (pre run)') 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)) runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2))
return 4 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) raise NotImplementedError(val_typ, val)
def _allocate_memory_stored_value( 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) offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val)
return adr 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) raise NotImplementedError(val_typ, val)
def _load_memory_stored_returned_value( 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) 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) raise NotImplementedError(ret_type3, wasm_value)
def _unpack(runner: runners.RunnerBase, typ: type3types.Type3, inp: bytes) -> Any: 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] return struct.unpack('<d', inp)[0]
if typ is type3types.bytes: 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 assert len(inp) == 4
adr = struct.unpack('<I', inp)[0] 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: if typ.base is type3types.tuple:
return _load_tuple_from_address(runner, typ, adr) 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) raise NotImplementedError(typ, inp)
def _load_bytes_from_address(runner: runners.RunnerBase, typ: type3types.Type3, adr: int) -> bytes: 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) _unpack(runner, arg_typ, arg_bytes)
for arg_typ, arg_bytes in zip(typ_list, _split_read_bytes(read_bytes, arg_sizes)) 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', 'Mismatch between applied types argument count',
'The type of the value returned from function constant should match its return type', '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: else:
expect_type_error( expect_type_error(
'Must be tuple', 'Must be tuple',
@ -175,6 +180,11 @@ if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
'Mismatch between applied types argument count', 'Mismatch between applied types argument count',
'The type of the value returned from function constant should match its return type', '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: else:
expect_type_error( expect_type_error(
TYPE_NAME + ' must be tuple (u32) instead', 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', 'Mismatch between applied types argument count',
'The type of the value returned from function select should match its return type', '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: else:
expect_type_error( expect_type_error(
TYPE_NAME + ' must be tuple (u32) instead', 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? # 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', '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: else:
expect_type_error( expect_type_error(
'Must be tuple', 'Must be tuple',
@ -325,6 +345,11 @@ if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
'Mismatch between applied types argument count', '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', '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: else:
expect_type_error( expect_type_error(
TYPE_NAME + ' must be tuple (u32) instead', TYPE_NAME + ' must be tuple (u32) instead',

View File

@ -1,3 +1,5 @@
from typing import Any, Dict
import functools import functools
import json import json
import sys import sys
@ -27,6 +29,9 @@ def get_tests(template):
def apply_settings(settings, txt): def apply_settings(settings, txt):
for k, v in settings.items(): for k, v in settings.items():
if k in ('CODE_HEADER', 'PYTHON'):
continue
txt = txt.replace(f'${k}', v) txt = txt.replace(f'${k}', v)
return txt 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_msg)} == exc_info.value.args[0][0].msg')
result.append(f'assert {repr(error_comment)} == exc_info.value.args[0][0].comment') 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): def generate_assertions(settings, result_code):
result = [] result = []
locals_ = { locals_ = {
'VAL0': eval(settings['VAL0']), 'TYPE': settings['TYPE'],
'TYPE_NAME': settings['TYPE_NAME'], 'TYPE_NAME': settings['TYPE_NAME'],
'expect': functools.partial(generate_assertion_expect, result), 'expect': functools.partial(generate_assertion_expect, result),
'expect_type_error': functools.partial(generate_assertion_expect_type_error, 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_) exec(result_code, {}, locals_)
return ' ' + '\n '.join(result) + '\n' return ' ' + '\n '.join(result) + '\n'
@ -93,9 +123,14 @@ def generate_code(markdown, template, settings):
print(' ' + user_story.replace('\n', '\n ')) print(' ' + user_story.replace('\n', '\n '))
print(' """') print(' """')
print(' code_py = """') print(' code_py = """')
if 'CODE_HEADER' in settings:
for lin in settings['CODE_HEADER']:
print(lin)
print()
print(inp_code.rstrip('\n')) print(inp_code.rstrip('\n'))
print('"""') print('"""')
print() print()
print(generate_assertions(settings, result_code)) print(generate_assertions(settings, result_code))
print() 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}
}
}