diff --git a/TODO.md b/TODO.md index e5cc852..f26a9b4 100644 --- a/TODO.md +++ b/TODO.md @@ -7,7 +7,6 @@ - Implement a trace() builtin for debugging - Check if we can use DataView in the Javascript examples, e.g. with setUint32 -- Storing u8 in memory still claims 32 bits (since that's what you need in local variables). However, using load8_u / loadu_s we can optimize this. - Implement a FizzBuzz example - Also, check the codes for FIXME and TODO - Allocation is done using pointers for members, is this desired? @@ -17,9 +16,11 @@ - Clean up Subscript implementation - it's half implemented in the compiler. Makes more sense to move more parts to stdlib_types. - Have a set of rules or guidelines for the constraint comments, they're messy. - Why is expression_subscript_bytes using a helper method but expression_subscript_static_array is not? +- calculate_alloc_size can be reworked; is_member isn't useful with TYPE_INFO_MAP - Parser is putting stuff in ModuleDataBlock - Surely the compiler should build data blocks + - Also put the struct.pack constants in TYPE_INFO_MAP - Make prelude more an actual thing - Merge in compiler.INSTANCES - Make it less build in - have a environment class of some kind diff --git a/phasm/compiler.py b/phasm/compiler.py index f13f41c..b758715 100644 --- a/phasm/compiler.py +++ b/phasm/compiler.py @@ -2,12 +2,13 @@ This module contains the code to convert parsed Ourlang into WebAssembly code """ import struct -from typing import List, Optional +from typing import List from . import codestyle, ourlang, prelude, wasm from .runtime import calculate_alloc_size, calculate_member_offset from .stdlib import alloc as stdlib_alloc from .stdlib import types as stdlib_types +from .stdlib.types import TYPE_INFO_CONSTRUCTED, TYPE_INFO_MAP from .type3.functions import TypeVariable from .type3.routers import NoRouteForTypeException, TypeApplicationRouter from .type3.typeclasses import Type3ClassMethod @@ -27,20 +28,6 @@ from .wasmgenerator import Generator as WasmGenerator TYPE3_ASSERTION_ERROR = 'You must call phasm_type3 after calling phasm_parse before your program can be compiled' -LOAD_STORE_TYPE_MAP = { - 'i8': 'i32', # Have to use an u32, since there is no native i8 type - 'u8': 'i32', # Have to use an u32, since there is no native u8 type - - 'i32': 'i32', - 'i64': 'i64', - 'u32': 'i32', - 'u64': 'i64', - 'f32': 'f32', - 'f64': 'f64', - - 'bytes': 'i32', # Bytes are passed around as pointers -} - def phasm_compile(inp: ourlang.Module) -> wasm.Module: """ Public method for compiling a parsed Phasm module into @@ -55,53 +42,8 @@ def type3(inp: Type3) -> wasm.WasmType: Types are used for example in WebAssembly function parameters and return types. """ - assert inp is not None, TYPE3_ASSERTION_ERROR - - if inp == prelude.none: - return wasm.WasmTypeNone() - - if inp == prelude.bool_: - # WebAssembly stores booleans as i32 - # See e.g. f32.eq, which is [f32 f32] -> [i32] - return wasm.WasmTypeInt32() - - if inp == prelude.u8: - # WebAssembly has only support for 32 and 64 bits - # So we need to store more memory per byte - return wasm.WasmTypeInt32() - - if inp == prelude.u32: - return wasm.WasmTypeInt32() - - if inp == prelude.u64: - return wasm.WasmTypeInt64() - - if inp == prelude.i8: - # WebAssembly has only support for 32 and 64 bits - # So we need to store more memory per byte - return wasm.WasmTypeInt32() - - if inp == prelude.i32: - return wasm.WasmTypeInt32() - - if inp == prelude.i64: - return wasm.WasmTypeInt64() - - if inp == prelude.f32: - return wasm.WasmTypeFloat32() - - if inp == prelude.f64: - return wasm.WasmTypeFloat64() - - if inp == prelude.bytes_: - # bytes are passed as pointer - # And pointers are i32 - return wasm.WasmTypeInt32() - - if (prelude.InternalPassAsPointer, (inp, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: - return wasm.WasmTypeInt32() - - raise NotImplementedError(type3, inp) + typ_info = TYPE_INFO_MAP.get(inp.name, TYPE_INFO_CONSTRUCTED) + return typ_info.wasm_type() def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.TupleInstantiation) -> None: """ @@ -121,7 +63,9 @@ def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Tu sa_type, = inp.type3.application.arguments args = tuple(sa_type for _ in inp.elements) - alloc_size = 4 + calculate_alloc_size(sa_type, is_member=False) * len(inp.elements) + # Can't use calculate_alloc_size directly since that doesn't + # know the dynamic array's length + alloc_size = 4 + calculate_alloc_size(sa_type, is_member=True) * len(inp.elements) alloc_size_header = len(inp.elements) elif isinstance(inp.type3.application, TypeApplication_TypeStar): # Possibly paranoid assert. If we have a future variadic type, @@ -165,15 +109,12 @@ def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Tu for element, exp_type3 in zip(inp.elements, args, strict=True): assert element.type3 == exp_type3 - if (prelude.InternalPassAsPointer, (exp_type3, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: - mtyp = 'i32' - else: - mtyp = LOAD_STORE_TYPE_MAP[exp_type3.name] + exp_type_info = TYPE_INFO_MAP.get(exp_type3.name, TYPE_INFO_CONSTRUCTED) wgn.add_statement('nop', comment='PRE') wgn.local.get(tmp_var) expression(wgn, mod, element) - wgn.add_statement(f'{mtyp}.store', 'offset=' + str(offset)) + wgn.add_statement(exp_type_info.wasm_store_func, 'offset=' + str(offset)) wgn.add_statement('nop', comment='POST') offset += calculate_alloc_size(exp_type3, is_member=True) @@ -213,14 +154,14 @@ def expression_subscript_static_array( with wgn.if_(): wgn.unreachable(comment='Out of bounds') + el_type_info = TYPE_INFO_MAP.get(el_type.name, TYPE_INFO_CONSTRUCTED) + wgn.local.get(tmp_var) - wgn.i32.const(calculate_alloc_size(el_type)) + wgn.i32.const(el_type_info.alloc_size) wgn.i32.mul() wgn.i32.add() - mtyp = LOAD_STORE_TYPE_MAP[el_type.name] - - wgn.add_statement(f'{mtyp}.load') + wgn.add_statement(el_type_info.wasm_load_func) def expression_subscript_tuple( attrs: tuple[WasmGenerator, ourlang.Module, ourlang.Subscript], @@ -234,19 +175,16 @@ def expression_subscript_tuple( offset = 0 for el_type in args[0:inp.index.value]: assert el_type is not None, TYPE3_ASSERTION_ERROR - offset += calculate_alloc_size(el_type) + el_type_info = TYPE_INFO_MAP.get(el_type.name, TYPE_INFO_CONSTRUCTED) + offset += el_type_info.alloc_size el_type = args[inp.index.value] assert el_type is not None, TYPE3_ASSERTION_ERROR expression(wgn, mod, inp.varref) - if (prelude.InternalPassAsPointer, (el_type, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: - mtyp = 'i32' - else: - mtyp = LOAD_STORE_TYPE_MAP[el_type.name] - - wgn.add_statement(f'{mtyp}.load', f'offset={offset}') + el_type_info = TYPE_INFO_MAP.get(el_type.name, TYPE_INFO_CONSTRUCTED) + wgn.add_statement(el_type_info.wasm_load_func, f'offset={offset}') SUBSCRIPT_ROUTER = TypeApplicationRouter[tuple[WasmGenerator, ourlang.Module, ourlang.Subscript], None]() SUBSCRIPT_ROUTER.add_n(prelude.bytes_, expression_subscript_bytes) @@ -305,7 +243,7 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Expression) if isinstance(inp.variable, ourlang.ModuleConstantDef): assert inp.type3 is not None, TYPE3_ASSERTION_ERROR - if (prelude.InternalPassAsPointer, (inp.type3, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: + if inp.type3.name not in TYPE_INFO_MAP: assert isinstance(inp.variable.constant, (ourlang.ConstantBytes, ourlang.ConstantStruct, ourlang.ConstantTuple, )) address = inp.variable.constant.data_block.address @@ -413,11 +351,10 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Expression) assert isinstance(inp.struct_type3.application, TypeApplication_Struct) member_type = dict(inp.struct_type3.application.arguments)[inp.member] - - mtyp = LOAD_STORE_TYPE_MAP[member_type.name] + member_type_info = TYPE_INFO_MAP.get(member_type.name, TYPE_INFO_CONSTRUCTED) expression(wgn, mod, inp.varref) - wgn.add_statement(f'{mtyp}.load', 'offset=' + str(calculate_member_offset( + wgn.add_statement(member_type_info.wasm_load_func, 'offset=' + str(calculate_member_offset( inp.struct_type3.name, inp.struct_type3.application.arguments, inp.member ))) return @@ -600,10 +537,8 @@ def function(mod: ourlang.Module, inp: ourlang.Function) -> wasm.Function: def module_data_u8(inp: int) -> bytes: """ Compile: module data, u8 value - - # FIXME: All u8 values are stored as u32 """ - return struct.pack(' bytes: """ @@ -620,10 +555,8 @@ def module_data_u64(inp: int) -> bytes: def module_data_i8(inp: int) -> bytes: """ Compile: module data, i8 value - - # FIXME: All i8 values are stored as i32 """ - return struct.pack(' bytes: """ @@ -812,18 +745,11 @@ def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstruc # Store each member individually for memname, mtyp3 in st_args: - mtyp: Optional[str] - if (prelude.InternalPassAsPointer, (mtyp3, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: - mtyp = 'i32' - else: - mtyp = LOAD_STORE_TYPE_MAP.get(mtyp3.name) - - if mtyp is None: - raise NotImplementedError(expression, inp, mtyp3) + mtyp3_info = TYPE_INFO_MAP.get(mtyp3.name, TYPE_INFO_CONSTRUCTED) wgn.local.get(tmp_var) wgn.add_statement('local.get', f'${memname}') - wgn.add_statement(f'{mtyp}.store', 'offset=' + str(calculate_member_offset( + wgn.add_statement(mtyp3_info.wasm_store_func, 'offset=' + str(calculate_member_offset( inp.struct_type3.name, st_args, memname ))) diff --git a/phasm/prelude/__init__.py b/phasm/prelude/__init__.py index 3b53e67..a440e54 100644 --- a/phasm/prelude/__init__.py +++ b/phasm/prelude/__init__.py @@ -16,7 +16,6 @@ from ..type3.functions import ( from ..type3.routers import TypeClassArgsRouter, TypeVariableLookup from ..type3.typeclasses import Type3Class, Type3ClassMethod from ..type3.types import ( - IntType3, Type3, TypeApplication_Nullary, TypeConstructor_Base, @@ -159,10 +158,7 @@ f64 = Type3('f64', TypeApplication_Nullary(None, None)) A 32-bits IEEE 754 float, of 64 bits width. """ -def da_on_create(args: tuple[Type3], typ: Type3) -> None: - instance_type_class(InternalPassAsPointer, typ) - -dynamic_array = TypeConstructor_DynamicArray('dynamic_array', on_create=da_on_create) +dynamic_array = TypeConstructor_DynamicArray('dynamic_array') """ This is a dynamic length piece of memory. @@ -170,10 +166,7 @@ It should be applied with two arguments. It has a runtime determined length, and each argument is the same. """ -def sa_on_create(args: tuple[Type3, IntType3], typ: Type3) -> None: - instance_type_class(InternalPassAsPointer, typ) - -static_array = TypeConstructor_StaticArray('static_array', on_create=sa_on_create) +static_array = TypeConstructor_StaticArray('static_array') """ This is a fixed length piece of memory. @@ -181,10 +174,7 @@ It should be applied with two arguments. It has a compile time determined length, and each argument is the same. """ -def tp_on_create(args: tuple[Type3, ...], typ: Type3) -> None: - instance_type_class(InternalPassAsPointer, typ) - -tuple_ = TypeConstructor_Tuple('tuple', on_create=tp_on_create) +tuple_ = TypeConstructor_Tuple('tuple') """ This is a fixed length piece of memory. @@ -192,22 +182,14 @@ It should be applied with zero or more arguments. It has a compile time determined length, and each argument can be different. """ -def fn_on_create(args: tuple[Type3, ...], typ: Type3) -> None: - # Not really a pointer; but still a i32 - # (It's actually a table lookup) - instance_type_class(InternalPassAsPointer, typ) - -function = TypeConstructor_Function('function', on_create=fn_on_create) +function = TypeConstructor_Function('function') """ This is a function. It should be applied with one or more arguments. The last argument is the 'return' type. """ -def st_on_create(args: tuple[tuple[str, Type3], ...], typ: Type3) -> None: - instance_type_class(InternalPassAsPointer, typ) - -struct = TypeConstructor_Struct('struct', on_create=st_on_create) +struct = TypeConstructor_Struct('struct') """ This is like a tuple, but each argument is named, so that developers can get and set fields by name. @@ -218,16 +200,6 @@ b = TypeVariable('b', TypeVariableApplication_Nullary(None, None)) t = TypeConstructorVariable('t') -InternalPassAsPointer = Type3Class('InternalPassAsPointer', (a, ), methods={}, operators={}) -""" -Internal type class to keep track which types we pass arounds as a pointer. -""" - -# instance_type_class(InternalPassAsPointer, bytes_) -# instance_type_class(InternalPassAsPointer, static_array) -# instance_type_class(InternalPassAsPointer, tuple_) -# instance_type_class(InternalPassAsPointer, struct) - Eq = Type3Class('Eq', (a, ), methods={}, operators={ '==': [a, a, bool_], '!=': [a, a, bool_], diff --git a/phasm/runtime.py b/phasm/runtime.py index bec58bf..d456971 100644 --- a/phasm/runtime.py +++ b/phasm/runtime.py @@ -1,11 +1,12 @@ from . import prelude +from .stdlib.types import TYPE_INFO_CONSTRUCTED, TYPE_INFO_MAP from .type3.routers import NoRouteForTypeException, TypeApplicationRouter from .type3.types import IntType3, Type3 def calculate_alloc_size_static_array(is_member: bool, args: tuple[Type3, IntType3]) -> int: if is_member: - return 4 + return TYPE_INFO_CONSTRUCTED.alloc_size sa_type, sa_len = args @@ -13,7 +14,7 @@ def calculate_alloc_size_static_array(is_member: bool, args: tuple[Type3, IntTyp def calculate_alloc_size_tuple(is_member: bool, args: tuple[Type3, ...]) -> int: if is_member: - return 4 + return TYPE_INFO_CONSTRUCTED.alloc_size return sum( calculate_alloc_size(x, is_member=True) @@ -22,7 +23,7 @@ def calculate_alloc_size_tuple(is_member: bool, args: tuple[Type3, ...]) -> int: def calculate_alloc_size_struct(is_member: bool, args: tuple[tuple[str, Type3], ...]) -> int: if is_member: - return 4 + return TYPE_INFO_CONSTRUCTED.alloc_size return sum( calculate_alloc_size(x, is_member=True) @@ -35,14 +36,9 @@ ALLOC_SIZE_ROUTER.add(prelude.struct, calculate_alloc_size_struct) ALLOC_SIZE_ROUTER.add(prelude.tuple_, calculate_alloc_size_tuple) def calculate_alloc_size(typ: Type3, is_member: bool = False) -> int: - if typ in (prelude.u8, prelude.i8, ): - return 4 # FIXME: We allocate 4 bytes for every u8 since you load them into an i32 - - if typ in (prelude.u32, prelude.i32, prelude.f32, ): - return 4 - - if typ in (prelude.u64, prelude.i64, prelude.f64, ): - return 8 + typ_info = TYPE_INFO_MAP.get(typ.name) + if typ_info is not None: + return typ_info.alloc_size try: return ALLOC_SIZE_ROUTER(is_member, typ) @@ -50,7 +46,7 @@ def calculate_alloc_size(typ: Type3, is_member: bool = False) -> int: if is_member: # By default, 'boxed' or 'constructed' types are # stored as pointers when a member of a struct or tuple - return 4 + return TYPE_INFO_CONSTRUCTED.alloc_size raise NotImplementedError(typ) diff --git a/phasm/stdlib/types.py b/phasm/stdlib/types.py index 77efc12..0f41d9c 100644 --- a/phasm/stdlib/types.py +++ b/phasm/stdlib/types.py @@ -1,13 +1,61 @@ """ stdlib: Standard types that are not wasm primitives """ +from typing import Mapping, NamedTuple, Type + from phasm.stdlib import alloc from phasm.type3.routers import TypeVariableLookup from phasm.type3.types import IntType3, Type3 +from phasm.wasm import ( + WasmType, + WasmTypeFloat32, + WasmTypeFloat64, + WasmTypeInt32, + WasmTypeInt64, + WasmTypeNone, +) from phasm.wasmgenerator import Generator, func_wrapper from phasm.wasmgenerator import VarType_i32 as i32 from phasm.wasmgenerator import VarType_i64 as i64 +TypeInfo = NamedTuple('TypeInfo', [ + # Name of the type + ('typ', str, ), + # What WebAssembly type to use when passing this value around + # For example in function arguments + ('wasm_type', Type[WasmType]), + # What WebAssembly function to use when loading a value from memory + ('wasm_load_func', str), + # What WebAssembly function to use when storing a value to memory + ('wasm_store_func', str), + # When storing this value in memory, how many bytes do we use? + # Only valid for non-constructed types, see calculate_alloc_size + # Should match wasm_load_func / wasm_store_func + ('alloc_size', int), +]) + +TYPE_INFO_MAP: Mapping[str, TypeInfo] = { + 'none': TypeInfo('none', WasmTypeNone, 'nop', 'nop', 0), + 'bool': TypeInfo('bool', WasmTypeInt32, 'i32.load8_u', 'i32.store8', 1), + + 'u8': TypeInfo('u8', WasmTypeInt32, 'i32.load8_u', 'i32.store8', 1), + 'i8': TypeInfo('i8', WasmTypeInt32, 'i32.load8_s', 'i32.store8', 1), + + 'u32': TypeInfo('u32', WasmTypeInt32, 'i32.load', 'i32.store', 4), + 'u64': TypeInfo('u64', WasmTypeInt64, 'i64.load', 'i64.store', 8), + 'i32': TypeInfo('i32', WasmTypeInt32, 'i32.load', 'i32.store', 4), + 'i64': TypeInfo('i64', WasmTypeInt64, 'i64.load', 'i64.store', 8), + 'f32': TypeInfo('f32', WasmTypeFloat32, 'f32.load', 'f32.store', 4), + 'f64': TypeInfo('f64', WasmTypeFloat64, 'f64.load', 'f64.store', 8), + 'ptr': TypeInfo('ptr', WasmTypeInt32, 'i32.load', 'i32.store', 4), +} + +# By default, constructed types are passed as pointers +# NOTE: ALLOC SIZE HERE DOES NOT WORK FOR CONSTRUCTED TYPES +# USE runtime.calculate_alloc_size FOR ACCURATE RESULTS +# Functions count as constructed types - even though they are +# not memory pointers but table addresses instead. +TYPE_INFO_CONSTRUCTED = TypeInfo('t a', WasmTypeInt32, 'i32.load', 'i32.store', 4) @func_wrapper() def __alloc_bytes__(g: Generator, length: i32) -> i32: @@ -1098,26 +1146,9 @@ def static_array_sum(g: Generator, tv_map: TypeVariableLookup) -> None: if sa_len.value < 1: raise NotImplementedError('Default value in case sum is empty') - # FIXME: We should probably use LOAD_STORE_TYPE_MAP for this? - mtyp_map = { - 'u32': 'i32', - 'u64': 'i64', - 'i32': 'i32', - 'i64': 'i64', - 'f32': 'f32', - 'f64': 'f64', - } - - # FIXME: We should probably use calc_alloc_size for this? - type_var_size_map = { - 'u32': 4, - 'u64': 8, - 'i32': 4, - 'i64': 8, - 'f32': 4, - 'f64': 8, - } + sa_type_info = TYPE_INFO_MAP.get(sa_type.name, TYPE_INFO_CONSTRUCTED) + # FIXME: This breaks when users start implementing their own NatNum classes type_var_add_generator = { 'u32': u32_natnum_add, 'u64': u64_natnum_add, @@ -1126,11 +1157,6 @@ def static_array_sum(g: Generator, tv_map: TypeVariableLookup) -> None: 'f32': f32_natnum_add, 'f64': f64_natnum_add, } - - # By default, constructed types are passed as pointers - # FIXME: We don't know what add function to call - sa_type_mtyp = mtyp_map.get(sa_type.name, 'i32') - sa_type_alloc_size = type_var_size_map.get(sa_type.name, 4) sa_type_add_gen = type_var_add_generator[sa_type.name] # Definitions @@ -1149,7 +1175,7 @@ def static_array_sum(g: Generator, tv_map: TypeVariableLookup) -> None: # Stack: [] g.nop(comment='Calculate address at which to stop looping') g.local.get(sum_adr) - g.i32.const(sa_len.value * sa_type_alloc_size) + g.i32.const(sa_len.value * sa_type_info.alloc_size) g.i32.add() g.local.set(sum_stop) @@ -1157,30 +1183,30 @@ def static_array_sum(g: Generator, tv_map: TypeVariableLookup) -> None: # Stack: [] -> [sum] g.nop(comment='Get the first array value as starting point') g.local.get(sum_adr) - g.add_statement(f'{sa_type_mtyp}.load') + g.add_statement(sa_type_info.wasm_load_func) # Since we did the first one, increase adr # adr = adr + sa_type_alloc_size # Stack: [sum] -> [sum] g.local.get(sum_adr) - g.i32.const(sa_type_alloc_size) + g.i32.const(sa_type_info.alloc_size) g.i32.add() g.local.set(sum_adr) if sa_len.value > 1: - with g.loop(params=[sa_type_mtyp], result=sa_type_mtyp): + with g.loop(params=[sa_type_info.wasm_type().to_wat()], result=sa_type_info.wasm_type().to_wat()): # sum = sum + *adr # Stack: [sum] -> [sum + *adr] g.nop(comment='Add array value') g.local.get(sum_adr) - g.add_statement(f'{sa_type_mtyp}.load') + g.add_statement(sa_type_info.wasm_load_func) sa_type_add_gen(g, {}) # adr = adr + sa_type_alloc_size # Stack: [sum] -> [sum] g.nop(comment='Calculate address of the next value') g.local.get(sum_adr) - g.i32.const(sa_type_alloc_size) + g.i32.const(sa_type_info.alloc_size) g.i32.add() g.local.tee(sum_adr) diff --git a/phasm/type3/types.py b/phasm/type3/types.py index cd9b187..e6bc4f2 100644 --- a/phasm/type3/types.py +++ b/phasm/type3/types.py @@ -3,7 +3,6 @@ Contains the final types for use in Phasm, as well as construtors. """ from typing import ( Any, - Callable, Hashable, Self, Tuple, @@ -141,27 +140,21 @@ class TypeConstructor_Base[T]: """ Base class for type construtors """ - __slots__ = ('name', 'on_create', '_cache', ) + __slots__ = ('name', '_cache', ) name: str """ The name of the type constructor """ - on_create: Callable[[T, Type3], None] - """ - Who to let know if a type is created - """ - _cache: dict[T, Type3] """ When constructing a type with the same arguments, it should produce the exact same result. """ - def __init__(self, name: str, on_create: Callable[[T, Type3], None]) -> None: + def __init__(self, name: str) -> None: self.name = name - self.on_create = on_create self._cache = {} @@ -188,7 +181,6 @@ class TypeConstructor_Base[T]: result = self._cache.get(key, None) if result is None: self._cache[key] = result = Type3(self.make_name(key), self.make_application(key)) - self.on_create(key, result) return result @@ -292,7 +284,6 @@ class TypeConstructor_Struct(TypeConstructor_Base[tuple[tuple[str, Type3], ...]] def __call__(self, name: str, args: tuple[tuple[str, Type3], ...]) -> Type3: result = Type3(name, self.make_application(args)) - self.on_create(args, result) return result class TypeApplication_Struct(TypeApplication_Base[TypeConstructor_Struct, tuple[tuple[str, Type3], ...]]): diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py index d444f74..dad1009 100644 --- a/tests/integration/helpers.py +++ b/tests/integration/helpers.py @@ -10,6 +10,7 @@ from phasm.runtime import ( calculate_alloc_size_struct, calculate_alloc_size_tuple, ) +from phasm.stdlib.types import TYPE_INFO_CONSTRUCTED, TYPE_INFO_MAP from phasm.type3 import types as type3types from phasm.type3.routers import NoRouteForTypeException, TypeApplicationRouter @@ -134,7 +135,7 @@ def _write_memory_stored_value( try: adr2 = ALLOCATE_MEMORY_STORED_ROUTER((runner, val), val_typ) runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2)) - return 4 + return TYPE_INFO_CONSTRUCTED.alloc_size except NoRouteForTypeException: to_write = WRITE_LOOKUP_MAP[val_typ.name](val) runner.interpreter_write_memory(adr, to_write) @@ -286,43 +287,36 @@ def _load_memory_stored_returned_value( return LOAD_FROM_ADDRESS_ROUTER((runner, wasm_value), ret_type3) def _unpack(runner: runners.RunnerBase, typ: type3types.Type3, inp: bytes) -> Any: + typ_info = TYPE_INFO_MAP.get(typ.name, TYPE_INFO_CONSTRUCTED) + + assert len(inp) == typ_info.alloc_size + if typ is prelude.u8: - # See compiler.py, LOAD_STORE_TYPE_MAP and module_data_u8 - assert len(inp) == 4 - return struct.unpack(' u8: assert exp_result == result.returned_value +@pytest.mark.integration_test +@pytest.mark.parametrize('type_, in_put, exp_result', [ + ('(u8, u8, u8, )', (45, 46, 47), 46, ), + ('u8[5]', (45, 46, 47, 48, 49), 46, ), + ('bytes', b'This is a test', 104) +]) +def test_subscript_1(type_, in_put, exp_result): + code_py = f""" +@exported +def testEntry(f: {type_}) -> u8: + return f[1] +""" + + result = Suite(code_py).run_code(in_put) + + assert exp_result == result.returned_value + @pytest.mark.integration_test @pytest.mark.parametrize('type_, in_put, exp_result', [ ('(u8, u8, u8, )', (45, 46, 47), 47, ), @@ -82,7 +99,7 @@ def testEntry(x: (u8, u32, u64)) -> u64: @pytest.mark.parametrize('type_, in_put', [ ('(u8, u8, )', (45, 46), ), ('u8[2]', (45, 46), ), - # bytes isn't known at runtime so works like normal + # dynamic_array isn't known at runtime so works like normal ]) def test_subscript_oob_constant_low(type_, in_put): code_py = f"""