From 46b06dbcf1e9053133641347bd490f45a3012ec7 Mon Sep 17 00:00:00 2001 From: "Johan B.W. de Vries" Date: Mon, 19 May 2025 21:04:13 +0200 Subject: [PATCH] Cleanup: TYPE_INFO_MAP This also removes the InternalPassAsPointer experiment. This also fixes that u8 values were stores as 32 bits in structs and tuples (but not dynamic arrays since that is special cased as bytes). Also, fixes allocation issue wi th dynamic arrays, it would allocate quadratic amount of memory. --- TODO.md | 3 +- phasm/compiler.py | 122 ++++-------------- phasm/prelude/__init__.py | 38 +----- phasm/runtime.py | 20 ++- phasm/stdlib/types.py | 86 +++++++----- phasm/type3/types.py | 13 +- tests/integration/helpers.py | 24 ++-- .../test_lang/test_subscriptable.py | 19 ++- 8 files changed, 124 insertions(+), 201 deletions(-) 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"""