From df0982918c85bc8213de32ef3c9a6fb79f19b70d Mon Sep 17 00:00:00 2001 From: "Johan B.W. de Vries" Date: Mon, 26 May 2025 19:25:14 +0200 Subject: [PATCH] Moves the prelude to runtime Previously, it was hardcoded at 'compile' time (in as much Python has that). This would make it more difficult to add stuff to it. Also, in a lot of places we made assumptions about prelude instead of checking properly. --- phasm/build/__init__.py | 0 phasm/build/base.py | 296 +++++++++ phasm/build/builtins.py | 22 + phasm/build/default.py | 111 ++++ phasm/codestyle.py | 11 +- phasm/compiler.py | 252 +++---- phasm/ourlang.py | 19 +- phasm/parser.py | 37 +- phasm/prelude/__init__.py | 772 ---------------------- phasm/runtime.py | 62 -- phasm/stdlib/types.py | 48 +- phasm/type3/constraints.py | 41 +- phasm/type3/constraintsgenerator.py | 2 +- phasm/type3/types.py | 2 +- tests/integration/helpers.py | 232 +++---- tests/integration/runners.py | 3 +- tests/integration/test_lang/test_bytes.py | 3 +- 17 files changed, 700 insertions(+), 1213 deletions(-) create mode 100644 phasm/build/__init__.py create mode 100644 phasm/build/base.py create mode 100644 phasm/build/builtins.py create mode 100644 phasm/build/default.py delete mode 100644 phasm/runtime.py diff --git a/phasm/build/__init__.py b/phasm/build/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/phasm/build/base.py b/phasm/build/base.py new file mode 100644 index 0000000..cc23336 --- /dev/null +++ b/phasm/build/base.py @@ -0,0 +1,296 @@ +""" +The base class for build environments. + +Contains nothing but the explicit compiler builtins. +""" +from typing import Any, Callable, NamedTuple, Type +from warnings import warn + +from ..type3.functions import ( + TypeConstructorVariable, + TypeVariable, +) +from ..type3.types import ( + IntType3, + Type3, + TypeConstructor_Base, + TypeConstructor_DynamicArray, + TypeConstructor_StaticArray, + TypeConstructor_Struct, + TypeConstructor_Tuple, +) +from ..type3.typeclasses import Type3Class, Type3ClassMethod +from ..type3.routers import ( + NoRouteForTypeException, + TypeApplicationRouter, + TypeClassArgsRouter, + TypeVariableLookup, +) +from ..wasm import WasmType, WasmTypeInt32 + +from . import builtins + + +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), + # When storing integers, the values can be stored as natural number + # (False) or as integer number (True). For other types, this is None. + ('signed', int | None), +]) + +class MissingImplementationWarning(Warning): + pass + +class BuildBase[G]: + __slots__ = ( + 'dynamic_array', + 'static_array', + 'struct', + 'tuple_', + + 'none_', + + 'type_info_map', + 'type_info_constructed', + + 'types', + 'type_class_instances', + 'type_class_instance_methods', + 'methods', + 'operators', + + 'alloc_size_router', + ) + + dynamic_array: TypeConstructor_DynamicArray + """ + This is a dynamic length piece of memory. + + It should be applied with two arguments. It has a runtime + determined length, and each argument is the same. + """ + + static_array: TypeConstructor_StaticArray + """ + This is a fixed length piece of memory. + + It should be applied with two arguments. It has a compile time + determined length, and each argument is the same. + """ + + struct: TypeConstructor_Struct + """ + This is like a tuple, but each argument is named, so that developers + can get and set fields by name. + """ + + tuple_: TypeConstructor_Tuple + """ + This is a fixed length piece of memory. + + It should be applied with zero or more arguments. It has a compile time + determined length, and each argument can be different. + """ + + none_: Type3 + """ + The none type, for when functions simply don't return anything. e.g., IO(). + """ + + type_info_map: dict[str, TypeInfo] + """ + Map from type name to the info of that type + """ + + type_info_constructed: TypeInfo + """ + By default, constructed types are passed as pointers + NOTE: ALLOC SIZE IN THIS STRUCT DOES NOT WORK FOR CONSTRUCTED TYPES + USE calculate_alloc_size FOR ACCURATE RESULTS + Functions count as constructed types - even though they are + not memory pointers but table addresses instead. + """ + + types: dict[str, Type3] + """ + Types that are available without explicit import. + """ + + type_class_instances: set[tuple[Type3Class, tuple[Type3 | TypeConstructor_Base[Any], ...]]] + """ + Type class instances that are available without explicit import. + """ + + type_class_instance_methods: dict[Type3ClassMethod, TypeClassArgsRouter[G, None]] + """ + Methods (and operators) for type class instances that are available without explicit import. + """ + + methods: dict[str, Type3ClassMethod] + """ + Methods that are available without explicit import. + """ + + operators: dict[str, Type3ClassMethod] + """ + Operators that are available without explicit import. + """ + + alloc_size_router: TypeApplicationRouter['BuildBase[G]', int] + """ + Helper value for calculate_alloc_size. + """ + + def __init__(self) -> None: + self.dynamic_array = builtins.dynamic_array + self.static_array = builtins.static_array + self.struct = builtins.struct + self.tuple_ = builtins.tuple_ + + self.none_ = builtins.none + + self.type_info_map = { + 'ptr': TypeInfo('ptr', WasmTypeInt32, 'i32.load', 'i32.store', 4, False), + } + self.type_info_constructed = self.type_info_map['ptr'] + + self.types = { + 'None': self.none_, + } + self.type_class_instances = set() + self.type_class_instance_methods = {} + self.methods = {} + self.operators = {} + + self.alloc_size_router = TypeApplicationRouter['BuildBase[G]', int]() + self.alloc_size_router.add(self.static_array, self.__class__.calculate_alloc_size_static_array) + self.alloc_size_router.add(self.struct, self.__class__.calculate_alloc_size_struct) + self.alloc_size_router.add(self.tuple_, self.__class__.calculate_alloc_size_tuple) + + def instance_type_class( + self, + cls: Type3Class, + *typ: Type3 | TypeConstructor_Base[Any], + methods: dict[str, Callable[[G, TypeVariableLookup], None]] = {}, + operators: dict[str, Callable[[G, TypeVariableLookup], None]] = {}, + ) -> None: + """ + Registered the given type class and its implementation + """ + assert len(cls.args) == len(typ) + + # TODO: Check for required existing instantiations + + # First just register the type + self.type_class_instances.add((cls, tuple(typ), )) + + # Then make the implementation findable + # We route based on the type class arguments. + tv_map: dict[TypeVariable, Type3] = {} + tc_map: dict[TypeConstructorVariable, TypeConstructor_Base[Any]] = {} + for arg_tv, arg_tp in zip(cls.args, typ, strict=True): + if isinstance(arg_tv, TypeVariable): + assert isinstance(arg_tp, Type3) + tv_map[arg_tv] = arg_tp + elif isinstance(arg_tv, TypeConstructorVariable): + assert isinstance(arg_tp, TypeConstructor_Base) + tc_map[arg_tv] = arg_tp + else: + raise NotImplementedError(arg_tv, arg_tp) + + for method_name, method in cls.methods.items(): + router = self.type_class_instance_methods.get(method) + if router is None: + router = TypeClassArgsRouter[G, None](cls.args) + self.type_class_instance_methods[method] = router + + try: + generator = methods[method_name] + except KeyError: + warn(MissingImplementationWarning(str(method), cls.name + ' ' + ' '.join(x.name for x in typ))) + continue + + router.add(tv_map, tc_map, generator) + + for operator_name, operator in cls.operators.items(): + router = self.type_class_instance_methods.get(operator) + if router is None: + router = TypeClassArgsRouter[G, None](cls.args) + self.type_class_instance_methods[operator] = router + + try: + generator = operators[operator_name] + except KeyError: + warn(MissingImplementationWarning(str(operator), cls.name + ' ' + ' '.join(x.name for x in typ))) + continue + + router.add(tv_map, tc_map, generator) + + + def calculate_alloc_size_static_array(self, args: tuple[Type3, IntType3]) -> int: + """ + Helper method for calculate_alloc_size - static_array + """ + sa_type, sa_len = args + + return sa_len.value * self.calculate_alloc_size(sa_type, is_member=True) + + def calculate_alloc_size_tuple(self, args: tuple[Type3, ...]) -> int: + """ + Helper method for calculate_alloc_size - tuple + """ + return sum( + self.calculate_alloc_size(x, is_member=True) + for x in args + ) + + def calculate_alloc_size_struct(self, args: tuple[tuple[str, Type3], ...]) -> int: + """ + Helper method for calculate_alloc_size - struct + """ + return sum( + self.calculate_alloc_size(x, is_member=True) + for _, x in args + ) + + def calculate_alloc_size(self, typ: Type3, is_member: bool = False) -> int: + """ + Calculates how much bytes you need to allocate when reserving memory for the given type. + """ + typ_info = self.type_info_map.get(typ.name) + if typ_info is not None: + return typ_info.alloc_size + + if is_member: + return self.type_info_constructed.alloc_size + + try: + return self.alloc_size_router(self, typ) + except NoRouteForTypeException: + raise NotImplementedError(typ) + + def calculate_member_offset(self, st_name: str, st_args: tuple[tuple[str, Type3], ...], needle: str) -> int: + """ + Calculates the amount of bytes that should be skipped in memory befor reaching the struct's property with the given name. + """ + result = 0 + + for memnam, memtyp in st_args: + if needle == memnam: + return result + + result += self.calculate_alloc_size(memtyp, is_member=True) + + raise Exception(f'{needle} not in {st_name}') diff --git a/phasm/build/builtins.py b/phasm/build/builtins.py new file mode 100644 index 0000000..4c5bff6 --- /dev/null +++ b/phasm/build/builtins.py @@ -0,0 +1,22 @@ +""" +Type (constructors) that are used integral to the compiler. + +These cannot be changed as there is compiler logic depending on them. + +For mode documentation, see base.py. +""" +from ..type3.types import ( + Type3, + TypeApplication_Nullary, + TypeConstructor_DynamicArray, + TypeConstructor_StaticArray, + TypeConstructor_Struct, + TypeConstructor_Tuple, +) + +dynamic_array = TypeConstructor_DynamicArray('dynamic_array') +static_array = TypeConstructor_StaticArray('static_array') +struct = TypeConstructor_Struct('struct') +tuple_ = TypeConstructor_Tuple('tuple') + +none = Type3('none', TypeApplication_Nullary(None, None)) diff --git a/phasm/build/default.py b/phasm/build/default.py new file mode 100644 index 0000000..7549f1d --- /dev/null +++ b/phasm/build/default.py @@ -0,0 +1,111 @@ +""" +The default class for build environments. + +Contains the compiler builtins as well as some sane defaults. + +# Added types + +f32: A 32-bits IEEE 754 float, of 32 bits width. +""" +from ..stdlib import types as stdlib_types +from ..type3.functions import ( + TypeVariable, + TypeVariableApplication_Nullary, +) +from ..type3.typeclasses import Type3Class +from ..type3.types import ( + Type3, + TypeApplication_Nullary, +) +from ..wasm import ( + WasmTypeFloat32, + WasmTypeFloat64, + WasmTypeInt32, + WasmTypeInt64, +) +from ..wasmgenerator import Generator + +from .base import BuildBase, TypeInfo + +class BuildDefault(BuildBase[Generator]): + def __init__(self) -> None: + super().__init__() + + u8 = Type3('u8', TypeApplication_Nullary(None, None)) + u16 = Type3('u16', TypeApplication_Nullary(None, None)) + u32 = Type3('u32', TypeApplication_Nullary(None, None)) + u64 = Type3('u64', TypeApplication_Nullary(None, None)) + i8 = Type3('i8', TypeApplication_Nullary(None, None)) + i16 = Type3('i16', TypeApplication_Nullary(None, None)) + i32 = Type3('i32', TypeApplication_Nullary(None, None)) + i64 = Type3('i64', TypeApplication_Nullary(None, None)) + f32 = Type3('f32', TypeApplication_Nullary(None, None)) + f64 = Type3('f64', TypeApplication_Nullary(None, None)) + + bytes_ = self.dynamic_array(u8) + + self.type_info_map.update({ + 'u8': TypeInfo('u8', WasmTypeInt32, 'i32.load8_u', 'i32.store8_u', 1, False), + 'u16': TypeInfo('u16', WasmTypeInt32, 'i32.load16_u', 'i32.store16_u', 2, False), + 'u32': TypeInfo('u32', WasmTypeInt32, 'i32.load', 'i32.store', 4, False), + 'u64': TypeInfo('u64', WasmTypeInt64, 'i64.load', 'i64.store', 8, False), + 'i8': TypeInfo('i8', WasmTypeInt32, 'i32.load8_s', 'i32.store8_s', 1, True), + 'i16': TypeInfo('i16', WasmTypeInt32, 'i32.load16_s', 'i32.store16_s', 2, True), + 'i32': TypeInfo('i32', WasmTypeInt32, 'i32.load', 'i32.store', 4, True), + 'i64': TypeInfo('i64', WasmTypeInt64, 'i64.load', 'i64.store', 8, True), + 'f32': TypeInfo('f32', WasmTypeFloat32, 'f32.load', 'f32.store', 4, None), + 'f64': TypeInfo('f64', WasmTypeFloat64, 'f64.load', 'f64.store', 8, None), + }) + + self.types.update({ + 'u8': u8, + 'u16': u16, + 'u32': u32, + 'u64': u64, + 'i8': i8, + 'i16': i16, + 'i32': i32, + 'i64': i64, + 'f32': f32, + 'f64': f64, + 'bytes': bytes_, + }) + + a = TypeVariable('a', TypeVariableApplication_Nullary(None, None)) + + NatNum = Type3Class('NatNum', (a, ), methods={}, operators={ + '+': [a, a, a], + '-': [a, a, a], + '*': [a, a, a], + '<<': [a, u32, a], # Arithmic shift left + '>>': [a, u32, a], # Arithmic shift right + }) + + self.instance_type_class(NatNum, i32, operators={ + '+': stdlib_types.i32_natnum_add, + '-': stdlib_types.i32_natnum_sub, + '*': stdlib_types.i32_natnum_mul, + '<<': stdlib_types.i32_natnum_arithmic_shift_left, + '>>': stdlib_types.i32_natnum_arithmic_shift_right, + }) + self.instance_type_class(NatNum, f32, operators={ + '+': stdlib_types.f32_natnum_add, + '-': stdlib_types.f32_natnum_sub, + '*': stdlib_types.f32_natnum_mul, + '<<': stdlib_types.f32_natnum_arithmic_shift_left, + '>>': stdlib_types.f32_natnum_arithmic_shift_right, + }) + self.instance_type_class(NatNum, f64, operators={ + '+': stdlib_types.f64_natnum_add, + '-': stdlib_types.f64_natnum_sub, + '*': stdlib_types.f64_natnum_mul, + '<<': stdlib_types.f64_natnum_arithmic_shift_left, + '>>': stdlib_types.f64_natnum_arithmic_shift_right, + }) + + self.methods.update({ + **NatNum.methods, + }) + self.operators.update({ + **NatNum.operators, + }) diff --git a/phasm/codestyle.py b/phasm/codestyle.py index 940ecfa..1209c70 100644 --- a/phasm/codestyle.py +++ b/phasm/codestyle.py @@ -3,13 +3,13 @@ This module generates source code based on the parsed AST It's intented to be a "any color, as long as it's black" kind of renderer """ -from typing import Generator +from typing import Any, Generator -from . import ourlang, prelude +from . import ourlang from .type3.types import Type3, TypeApplication_Struct -def phasm_render(inp: ourlang.Module) -> str: +def phasm_render(inp: ourlang.Module[Any]) -> str: """ Public method for rendering a Phasm module into Phasm code """ @@ -21,9 +21,6 @@ def type3(inp: Type3) -> str: """ Render: type's name """ - if inp is prelude.none: - return 'None' - return inp.name def struct_definition(inp: ourlang.StructDefinition) -> str: @@ -161,7 +158,7 @@ def function(inp: ourlang.Function) -> str: return result -def module(inp: ourlang.Module) -> str: +def module(inp: ourlang.Module[Any]) -> str: """ Render: Module """ diff --git a/phasm/compiler.py b/phasm/compiler.py index e2df411..27a87bb 100644 --- a/phasm/compiler.py +++ b/phasm/compiler.py @@ -5,7 +5,8 @@ import struct from typing import List from . import ourlang, prelude, wasm -from .runtime import calculate_alloc_size, calculate_member_offset +from .build import builtins +from .build.base import TypeInfo from .stdlib import alloc as stdlib_alloc from .stdlib import types as stdlib_types from .stdlib.types import TYPE_INFO_CONSTRUCTED, TYPE_INFO_MAP @@ -24,11 +25,17 @@ from .type3.types import ( TypeConstructor_StaticArray, TypeConstructor_Tuple, ) +from .wasm import ( + WasmTypeInt32, + WasmTypeInt64, + WasmTypeFloat32, + WasmTypeFloat64, +) from .wasmgenerator import Generator as WasmGenerator TYPE3_ASSERTION_ERROR = 'You must call phasm_type3 after calling phasm_parse before your program can be compiled' -def phasm_compile(inp: ourlang.Module) -> wasm.Module: +def phasm_compile(inp: ourlang.Module[WasmGenerator]) -> wasm.Module: """ Public method for compiling a parsed Phasm module into a WebAssembly module @@ -45,7 +52,7 @@ def type3(inp: Type3) -> wasm.WasmType: 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: +def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourlang.TupleInstantiation) -> None: """ Compile: Instantiation (allocation) of a tuple """ @@ -65,7 +72,7 @@ def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Tu args = tuple(sa_type for _ in 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 = 4 + mod.build.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, @@ -73,7 +80,7 @@ def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Tu assert isinstance(inp.type3.application.constructor, TypeConstructor_Tuple) args = inp.type3.application.arguments - alloc_size = calculate_alloc_size(inp.type3, is_member=False) + alloc_size = mod.build.calculate_alloc_size(inp.type3, is_member=False) elif isinstance(inp.type3.application, TypeApplication_TypeInt): # Possibly paranoid assert. If we have a future type of kind * -> Int -> *, # does it also do this tuple instantation like this? @@ -82,7 +89,7 @@ def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Tu sa_type, sa_len = inp.type3.application.arguments args = tuple(sa_type for _ in range(sa_len.value)) - alloc_size = calculate_alloc_size(inp.type3, is_member=False) + alloc_size = mod.build.calculate_alloc_size(inp.type3, is_member=False) else: raise NotImplementedError('tuple_instantiation', inp.type3) @@ -117,13 +124,13 @@ def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Tu 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) + offset += mod.build.calculate_alloc_size(exp_type3, is_member=True) # Return the allocated address wgn.local.get(tmp_var) def expression_subscript_bytes( - attrs: tuple[WasmGenerator, ourlang.Module, ourlang.Subscript], + attrs: tuple[WasmGenerator, ourlang.Module[WasmGenerator], ourlang.Subscript], ) -> None: wgn, mod, inp = attrs @@ -132,7 +139,7 @@ def expression_subscript_bytes( wgn.call(stdlib_types.__subscript_bytes__) def expression_subscript_static_array( - attrs: tuple[WasmGenerator, ourlang.Module, ourlang.Subscript], + attrs: tuple[WasmGenerator, ourlang.Module[WasmGenerator], ourlang.Subscript], args: tuple[Type3, IntType3], ) -> None: wgn, mod, inp = attrs @@ -164,7 +171,7 @@ def expression_subscript_static_array( wgn.add_statement(el_type_info.wasm_load_func) def expression_subscript_tuple( - attrs: tuple[WasmGenerator, ourlang.Module, ourlang.Subscript], + attrs: tuple[WasmGenerator, ourlang.Module[WasmGenerator], ourlang.Subscript], args: tuple[Type3, ...], ) -> None: wgn, mod, inp = attrs @@ -186,12 +193,12 @@ def expression_subscript_tuple( 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) -SUBSCRIPT_ROUTER.add(prelude.static_array, expression_subscript_static_array) -SUBSCRIPT_ROUTER.add(prelude.tuple_, expression_subscript_tuple) +SUBSCRIPT_ROUTER = TypeApplicationRouter[tuple[WasmGenerator, ourlang.Module[WasmGenerator], ourlang.Subscript], None]() +# SUBSCRIPT_ROUTER.add(builtins.dynamic_array, expression_subscript_dynamic_array) +SUBSCRIPT_ROUTER.add(builtins.static_array, expression_subscript_static_array) +SUBSCRIPT_ROUTER.add(builtins.tuple_, expression_subscript_tuple) -def expression(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Expression) -> None: +def expression(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourlang.Expression) -> None: """ Compile: Any expression """ @@ -202,34 +209,23 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Expression) if isinstance(inp, ourlang.ConstantPrimitive): assert inp.type3 is not None, TYPE3_ASSERTION_ERROR - if inp.type3 in (prelude.i8, prelude.u8, ): - # No native u8 type - treat as i32, with caution + type_info = mod.build.type_info_map[inp.type3.name] + if type_info.wasm_type is WasmTypeInt32: assert isinstance(inp.value, int) wgn.i32.const(inp.value) return - if inp.type3 in (prelude.i16, prelude.u16, ): - # No native u16 type - treat as i32, with caution - assert isinstance(inp.value, int) - wgn.i32.const(inp.value) - return - - if inp.type3 in (prelude.i32, prelude.u32, ): - assert isinstance(inp.value, int) - wgn.i32.const(inp.value) - return - - if inp.type3 in (prelude.i64, prelude.u64, ): + if type_info.wasm_type is WasmTypeInt64: assert isinstance(inp.value, int) wgn.i64.const(inp.value) return - if inp.type3 == prelude.f32: + if type_info.wasm_type is WasmTypeFloat32: assert isinstance(inp.value, float) wgn.f32.const(inp.value) return - if inp.type3 == prelude.f64: + if type_info.wasm_type is WasmTypeFloat64: assert isinstance(inp.value, float) wgn.f64.const(inp.value) return @@ -285,7 +281,7 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Expression) raise NotImplementedError(type_var, arg_expr.type3) - router = prelude.PRELUDE_TYPE_CLASS_INSTANCE_METHODS[inp.operator] + router = mod.build.type_class_instance_methods[inp.operator] router(wgn, type_var_map) return @@ -314,7 +310,7 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Expression) raise NotImplementedError(type_var, arg_expr.type3) - router = prelude.PRELUDE_TYPE_CLASS_INSTANCE_METHODS[inp.function] + router = mod.build.type_class_instance_methods[inp.function] try: router(wgn, type_var_map) except NoRouteForTypeException: @@ -367,14 +363,14 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Expression) member_type_info = TYPE_INFO_MAP.get(member_type.name, TYPE_INFO_CONSTRUCTED) expression(wgn, mod, inp.varref) - wgn.add_statement(member_type_info.wasm_load_func, 'offset=' + str(calculate_member_offset( + wgn.add_statement(member_type_info.wasm_load_func, 'offset=' + str(mod.build.calculate_member_offset( inp.struct_type3.name, inp.struct_type3.application.arguments, inp.member ))) return raise NotImplementedError(expression, inp) -def statement_return(wgn: WasmGenerator, mod: ourlang.Module, fun: ourlang.Function, inp: ourlang.StatementReturn) -> None: +def statement_return(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], fun: ourlang.Function, inp: ourlang.StatementReturn) -> None: """ Compile: Return statement """ @@ -391,7 +387,7 @@ def statement_return(wgn: WasmGenerator, mod: ourlang.Module, fun: ourlang.Funct expression(wgn, mod, inp.value) wgn.return_() -def statement_if(wgn: WasmGenerator, mod: ourlang.Module, fun: ourlang.Function, inp: ourlang.StatementIf) -> None: +def statement_if(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], fun: ourlang.Function, inp: ourlang.StatementIf) -> None: """ Compile: If statement """ @@ -406,7 +402,7 @@ def statement_if(wgn: WasmGenerator, mod: ourlang.Module, fun: ourlang.Function, # for stat in inp.else_statements: # statement(wgn, stat) -def statement(wgn: WasmGenerator, mod: ourlang.Module, fun: ourlang.Function, inp: ourlang.Statement) -> None: +def statement(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], fun: ourlang.Function, inp: ourlang.Statement) -> None: """ Compile: any statement """ @@ -446,7 +442,7 @@ def import_(inp: ourlang.Function) -> wasm.Import: type3(inp.returns_type3) ) -def function(mod: ourlang.Module, inp: ourlang.Function) -> wasm.Function: +def function(mod: ourlang.Module[WasmGenerator], inp: ourlang.Function) -> wasm.Function: """ Compile: function """ @@ -455,7 +451,7 @@ def function(mod: ourlang.Module, inp: ourlang.Function) -> wasm.Function: wgn = WasmGenerator() if isinstance(inp, ourlang.StructConstructor): - _generate_struct_constructor(wgn, inp) + _generate_struct_constructor(wgn, mod, inp) else: for stat in inp.statements: statement(wgn, mod, inp, stat) @@ -475,71 +471,30 @@ def function(mod: ourlang.Module, inp: ourlang.Function) -> wasm.Function: wgn.statements ) -def module_data_u8(inp: int) -> bytes: - """ - Compile: module data, u8 value - """ - return struct.pack(' bytes: + letter_map = { + (WasmTypeInt32, 1, False): 'B', + (WasmTypeInt32, 1, True): 'b', + (WasmTypeInt32, 2, False): 'H', + (WasmTypeInt32, 2, True): 'h', + (WasmTypeInt32, 4, False): 'I', + (WasmTypeInt32, 4, True): 'i', + (WasmTypeInt64, 8, False): 'Q', + (WasmTypeInt64, 8, True): 'q', + (WasmTypeFloat32, 4, None): 'f', + (WasmTypeFloat32, 8, None): 'd', + } -def module_data_u16(inp: int) -> bytes: - """ - Compile: module data, u16 value - """ - return struct.pack(' bytes: - """ - Compile: module data, u32 value - """ - return struct.pack(' bytes: - """ - Compile: module data, u64 value - """ - return struct.pack(' bytes: - """ - Compile: module data, i8 value - """ - return struct.pack(' bytes: - """ - Compile: module data, i16 value - """ - return struct.pack(' bytes: - """ - Compile: module data, i32 value - """ - return struct.pack(' bytes: - """ - Compile: module data, i64 value - """ - return struct.pack(' bytes: - """ - Compile: module data, f32 value - """ - return struct.pack(' bytes: - """ - Compile: module data, f64 value - """ - return struct.pack(' bytes: +def module_data(mod: ourlang.Module[WasmGenerator], inp: ourlang.ModuleData) -> bytes: """ Compile: module data """ unalloc_ptr = stdlib_alloc.UNALLOC_PTR + u32_type_info = mod.build.type_info_map['u32'] + ptr_type_info = mod.build.type_info_map['ptr'] allocated_data = b'' @@ -551,112 +506,55 @@ def module_data(inp: ourlang.ModuleData) -> bytes: for constant in block.data: assert constant.type3 is not None, TYPE3_ASSERTION_ERROR - if isinstance(constant, ourlang.ConstantMemoryStored) and block is not constant.data_block: + if isinstance(constant, ourlang.ConstantBytes): + data_list.append(module_data_primitive(u32_type_info, len(constant.value))) + data_list.append(constant.value) + continue + + if isinstance(constant, ourlang.ConstantMemoryStored): + if block is constant.data_block: + raise NotImplementedError(block, constant) + # It's stored in a different block # We only need to store its address # This happens for example when a tuple refers # to a bytes constant assert constant.data_block.address is not None, 'Referred memory not yet stored' - data_list.append(module_data_u32(constant.data_block.address)) + data_list.append(module_data_primitive(ptr_type_info, constant.data_block.address)) continue - if constant.type3 == prelude.u8: - assert isinstance(constant, ourlang.ConstantPrimitive) - assert isinstance(constant.value, int) - data_list.append(module_data_u8(constant.value)) - continue + type_info = mod.build.type_info_map[constant.type3.name] - if constant.type3 == prelude.u16: - assert isinstance(constant, ourlang.ConstantPrimitive) - assert isinstance(constant.value, int) - data_list.append(module_data_u16(constant.value)) - continue - - if constant.type3 == prelude.u32: - assert isinstance(constant, ourlang.ConstantPrimitive) - assert isinstance(constant.value, int) - data_list.append(module_data_u32(constant.value)) - continue - - if constant.type3 == prelude.u64: - assert isinstance(constant, ourlang.ConstantPrimitive) - assert isinstance(constant.value, int) - data_list.append(module_data_u64(constant.value)) - continue - - if constant.type3 == prelude.i8: - assert isinstance(constant, ourlang.ConstantPrimitive) - assert isinstance(constant.value, int) - data_list.append(module_data_i8(constant.value)) - continue - - if constant.type3 == prelude.i16: - assert isinstance(constant, ourlang.ConstantPrimitive) - assert isinstance(constant.value, int) - data_list.append(module_data_i16(constant.value)) - continue - - if constant.type3 == prelude.i32: - assert isinstance(constant, ourlang.ConstantPrimitive) - assert isinstance(constant.value, int) - data_list.append(module_data_i32(constant.value)) - continue - - if constant.type3 == prelude.i64: - assert isinstance(constant, ourlang.ConstantPrimitive) - assert isinstance(constant.value, int) - data_list.append(module_data_i64(constant.value)) - continue - - if constant.type3 == prelude.f32: - assert isinstance(constant, ourlang.ConstantPrimitive) - assert isinstance(constant.value, float) - data_list.append(module_data_f32(constant.value)) - continue - - if constant.type3 == prelude.f64: - assert isinstance(constant, ourlang.ConstantPrimitive) - assert isinstance(constant.value, float) - data_list.append(module_data_f64(constant.value)) - continue - - if constant.type3 == prelude.bytes_: - assert isinstance(constant, ourlang.ConstantBytes) - assert isinstance(constant.value, bytes) - data_list.append(module_data_u32(len(constant.value))) - data_list.append(constant.value) - continue - - raise NotImplementedError(constant, constant.type3) + return module_data_primitive(type_info, constant.value) block_data = b''.join(data_list) - allocated_data += module_data_u32(len(block_data)) + block_data + allocated_data += module_data_primitive(u32_type_info, len(block_data)) + block_data unalloc_ptr += 4 + len(block_data) return ( # Store that we've initialized the memory - module_data_u32(stdlib_alloc.IDENTIFIER) + module_data_primitive(u32_type_info, stdlib_alloc.IDENTIFIER) # Store the first reserved i32 - + module_data_u32(0) + + module_data_primitive(u32_type_info, 0) # Store the pointer towards the first free block # In this case, 0 since we haven't freed any blocks yet - + module_data_u32(0) + + module_data_primitive(u32_type_info, 0) # Store the pointer towards the first unallocated block # In this case the end of the stdlib.alloc header at the start - + module_data_u32(unalloc_ptr) + + module_data_primitive(u32_type_info, unalloc_ptr) # Store the actual data + allocated_data ) -def module(inp: ourlang.Module) -> wasm.Module: +def module(inp: ourlang.Module[WasmGenerator]) -> wasm.Module: """ Compile: module """ result = wasm.Module() - result.memory.data = module_data(inp.data) + result.memory.data = module_data(inp, inp.data) result.imports = [ import_(x) @@ -698,7 +596,7 @@ def module(inp: ourlang.Module) -> wasm.Module: return result -def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstructor) -> None: +def _generate_struct_constructor(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourlang.StructConstructor) -> None: assert isinstance(inp.struct_type3.application, TypeApplication_Struct) st_args = inp.struct_type3.application.arguments @@ -706,7 +604,7 @@ def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstruc tmp_var = wgn.temp_var_i32('struct_adr') # Allocated the required amounts of bytes in memory - wgn.i32.const(calculate_alloc_size(inp.struct_type3)) + wgn.i32.const(mod.build.calculate_alloc_size(inp.struct_type3)) wgn.call(stdlib_alloc.__alloc__) wgn.local.set(tmp_var) @@ -716,7 +614,7 @@ def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstruc wgn.local.get(tmp_var) wgn.add_statement('local.get', f'${memname}') - wgn.add_statement(mtyp3_info.wasm_store_func, 'offset=' + str(calculate_member_offset( + wgn.add_statement(mtyp3_info.wasm_store_func, 'offset=' + str(mod.build.calculate_member_offset( inp.struct_type3.name, st_args, memname ))) diff --git a/phasm/ourlang.py b/phasm/ourlang.py index 2bd3cac..f5cb765 100644 --- a/phasm/ourlang.py +++ b/phasm/ourlang.py @@ -4,6 +4,7 @@ Contains the syntax tree for ourlang from typing import Dict, Iterable, List, Optional, Union from . import prelude +from .build.base import BuildBase from .type3.functions import FunctionSignature, TypeVariableContext from .type3.typeclasses import Type3ClassMethod from .type3.types import Type3, TypeApplication_Struct @@ -288,14 +289,14 @@ class Function: returns_type3: Type3 posonlyargs: List[FunctionParam] - def __init__(self, name: str, lineno: int) -> None: + def __init__(self, name: str, lineno: int, returns_type3: Type3) -> None: self.name = name self.lineno = lineno self.exported = False self.imported = None self.statements = [] self.signature = FunctionSignature(TypeVariableContext(), []) - self.returns_type3 = prelude.none # FIXME: This could be a placeholder + self.returns_type3 = returns_type3 self.posonlyargs = [] class StructDefinition: @@ -323,7 +324,7 @@ class StructConstructor(Function): struct_type3: Type3 def __init__(self, struct_type3: Type3) -> None: - super().__init__(f'@{struct_type3.name}@__init___@', -1) + super().__init__(f'@{struct_type3.name}@__init___@', -1, struct_type3) assert isinstance(struct_type3.application, TypeApplication_Struct) @@ -331,7 +332,6 @@ class StructConstructor(Function): self.posonlyargs.append(FunctionParam(mem, typ, )) self.signature.args.append(typ) - self.returns_type3 = struct_type3 self.signature.args.append(struct_type3) self.struct_type3 = struct_type3 @@ -377,25 +377,30 @@ class ModuleData: def __init__(self) -> None: self.blocks = [] -class Module: +class Module[G]: """ A module is a file and consists of functions """ - __slots__ = ('data', 'types', 'struct_definitions', 'constant_defs', 'functions', 'operators', 'functions_table', ) + __slots__ = ('build', 'data', 'types', 'struct_definitions', 'constant_defs', 'functions', 'methods', 'operators', 'functions_table', ) + build: BuildBase[G] data: ModuleData types: dict[str, Type3] struct_definitions: Dict[str, StructDefinition] constant_defs: Dict[str, ModuleConstantDef] functions: Dict[str, Function] + methods: Dict[str, Type3ClassMethod] operators: Dict[str, Type3ClassMethod] functions_table: dict[Function, int] - def __init__(self) -> None: + def __init__(self, build: BuildBase[G]) -> None: + self.build = build + self.data = ModuleData() self.types = {} self.struct_definitions = {} self.constant_defs = {} self.functions = {} + self.methods = {} self.operators = {} self.functions_table = {} diff --git a/phasm/parser.py b/phasm/parser.py index e72946d..f7ef71b 100644 --- a/phasm/parser.py +++ b/phasm/parser.py @@ -5,6 +5,8 @@ import ast from typing import Any, Dict, NoReturn, Union from . import prelude +from .build.base import BuildBase +from .build.default import BuildDefault from .exceptions import StaticError from .ourlang import ( AccessStructMember, @@ -31,12 +33,12 @@ from .ourlang import ( TupleInstantiation, VariableReference, ) -from .prelude import PRELUDE_METHODS, PRELUDE_OPERATORS, PRELUDE_TYPES from .type3.typeclasses import Type3ClassMethod from .type3.types import IntType3, Type3 +from .wasmgenerator import Generator -def phasm_parse(source: str) -> Module: +def phasm_parse(source: str) -> Module[Generator]: """ Public method for parsing Phasm code into a Phasm Module """ @@ -44,7 +46,8 @@ def phasm_parse(source: str) -> Module: res = OptimizerTransformer().visit(res) - our_visitor = OurVisitor() + build = BuildDefault() + our_visitor = OurVisitor(build) return our_visitor.visit_Module(res) OurLocals = Dict[str, Union[FunctionParam]] # FIXME: Does it become easier if we add ModuleConstantDef to this dict? @@ -75,7 +78,7 @@ class OptimizerTransformer(ast.NodeTransformer): return node.operand return node -class OurVisitor: +class OurVisitor[G]: """ Class to visit a Python syntax tree and create an ourlang syntax tree @@ -90,14 +93,14 @@ class OurVisitor: # pylint: disable=C0103,C0116,C0301,R0201,R0912 - def __init__(self) -> None: - pass + def __init__(self, build: BuildBase[G]) -> None: + self.build = build - def visit_Module(self, node: ast.Module) -> Module: - module = Module() + def visit_Module(self, node: ast.Module) -> Module[G]: + module = Module(self.build) - module.operators.update(PRELUDE_OPERATORS) - module.types.update(PRELUDE_TYPES) + module.operators.update(self.build.operators) + module.types.update(self.build.types) _not_implemented(not node.type_ignores, 'Module.type_ignores') @@ -140,7 +143,7 @@ class OurVisitor: return module - def pre_visit_Module_stmt(self, module: Module, node: ast.stmt) -> Union[Function, StructDefinition, ModuleConstantDef]: + def pre_visit_Module_stmt(self, module: Module[G], node: ast.stmt) -> Union[Function, StructDefinition, ModuleConstantDef]: if isinstance(node, ast.FunctionDef): return self.pre_visit_Module_FunctionDef(module, node) @@ -153,7 +156,7 @@ class OurVisitor: raise NotImplementedError(f'{node} on Module') def pre_visit_Module_FunctionDef(self, module: Module, node: ast.FunctionDef) -> Function: - function = Function(node.name, node.lineno) + function = Function(node.name, node.lineno, self.build.none_) _not_implemented(not node.args.posonlyargs, 'FunctionDef.args.posonlyargs') @@ -477,8 +480,8 @@ class OurVisitor: func: Union[Function, FunctionParam, Type3ClassMethod] - if node.func.id in PRELUDE_METHODS: - func = PRELUDE_METHODS[node.func.id] + if node.func.id in module.methods: + func = module.methods[node.func.id] elif node.func.id in our_locals: func = our_locals[node.func.id] else: @@ -637,7 +640,7 @@ class OurVisitor: _raise_static_error(node, 'Must subscript using a constant index') if node.slice.value is Ellipsis: - return prelude.dynamic_array( + return module.build.dynamic_array( self.visit_type(module, node.value), ) @@ -646,7 +649,7 @@ class OurVisitor: if not isinstance(node.ctx, ast.Load): _raise_static_error(node, 'Must be load context') - return prelude.static_array( + return module.build.static_array( self.visit_type(module, node.value), IntType3(node.slice.value), ) @@ -655,7 +658,7 @@ class OurVisitor: if not isinstance(node.ctx, ast.Load): _raise_static_error(node, 'Must be load context') - return prelude.tuple_( + return module.build.tuple_( *(self.visit_type(module, elt) for elt in node.elts) ) diff --git a/phasm/prelude/__init__.py b/phasm/prelude/__init__.py index f651ff9..e69de29 100644 --- a/phasm/prelude/__init__.py +++ b/phasm/prelude/__init__.py @@ -1,772 +0,0 @@ -""" -The prelude are all the builtin types, type classes and methods -""" -from typing import Any, Callable -from warnings import warn - -from phasm.stdlib import types as stdtypes -from phasm.wasmgenerator import Generator - -from ..type3.functions import ( - Constraint_TypeClassInstanceExists, - TypeConstructorVariable, - TypeVariable, - TypeVariableApplication_Nullary, -) -from ..type3.routers import TypeClassArgsRouter, TypeVariableLookup -from ..type3.typeclasses import Type3Class, Type3ClassMethod -from ..type3.types import ( - Type3, - TypeApplication_Nullary, - TypeConstructor_Base, - TypeConstructor_DynamicArray, - TypeConstructor_Function, - TypeConstructor_StaticArray, - TypeConstructor_Struct, - TypeConstructor_Tuple, -) - -PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: set[tuple[Type3Class, tuple[Type3 | TypeConstructor_Base[Any], ...]]] = set() - -PRELUDE_TYPE_CLASS_INSTANCE_METHODS: dict[Type3ClassMethod, TypeClassArgsRouter[Generator, None]] = {} - -class MissingImplementationException(Exception): - pass - -class MissingImplementationWarning(Warning): - pass - -def instance_type_class( - cls: Type3Class, - *typ: Type3 | TypeConstructor_Base[Any], - methods: dict[str, Callable[[Generator, TypeVariableLookup], None]] = {}, - operators: dict[str, Callable[[Generator, TypeVariableLookup], None]] = {}, -) -> None: - global PRELUDE_TYPE_CLASS_INSTANCES_EXISTING - global PRELUDE_TYPE_CLASS_INSTANCE_METHODS - - assert len(cls.args) == len(typ) - - tv_map: dict[TypeVariable, Type3] = {} - tc_map: dict[TypeConstructorVariable, TypeConstructor_Base[Any]] = {} - for arg_tv, arg_tp in zip(cls.args, typ, strict=True): - if isinstance(arg_tv, TypeVariable): - assert isinstance(arg_tp, Type3) - tv_map[arg_tv] = arg_tp - elif isinstance(arg_tv, TypeConstructorVariable): - assert isinstance(arg_tp, TypeConstructor_Base) - tc_map[arg_tv] = arg_tp - else: - raise NotImplementedError(arg_tv, arg_tp) - - # TODO: Check for required existing instantiations - - PRELUDE_TYPE_CLASS_INSTANCES_EXISTING.add((cls, tuple(typ), )) - - for method_name, method in cls.methods.items(): - router = PRELUDE_TYPE_CLASS_INSTANCE_METHODS.get(method) - if router is None: - router = TypeClassArgsRouter[Generator, None](cls.args) - PRELUDE_TYPE_CLASS_INSTANCE_METHODS[method] = router - - try: - generator = methods[method_name] - except KeyError: - warn(MissingImplementationWarning(str(method), cls.name + ' ' + ' '.join(x.name for x in typ))) - continue - - router.add(tv_map, tc_map, generator) - - for operator_name, operator in cls.operators.items(): - router = PRELUDE_TYPE_CLASS_INSTANCE_METHODS.get(operator) - if router is None: - router = TypeClassArgsRouter[Generator, None](cls.args) - PRELUDE_TYPE_CLASS_INSTANCE_METHODS[operator] = router - - try: - generator = operators[operator_name] - except KeyError: - warn(MissingImplementationWarning(str(operator), cls.name + ' ' + ' '.join(x.name for x in typ))) - continue - - router.add(tv_map, tc_map, generator) - -none = Type3('none', TypeApplication_Nullary(None, None)) -""" -The none type, for when functions simply don't return anything. e.g., IO(). -""" - -bool_ = Type3('bool', TypeApplication_Nullary(None, None)) -""" -The bool type, either True or False - -Suffixes with an underscores, as it's a Python builtin -""" - -u8 = Type3('u8', TypeApplication_Nullary(None, None)) -""" -The unsigned 8-bit integer type. - -Operations on variables employ modular arithmetic, with modulus 2^8. -""" - -u16 = Type3('u16', TypeApplication_Nullary(None, None)) -""" -The unsigned 16-bit integer type. - -Operations on variables employ modular arithmetic, with modulus 2^16. -""" - -u32 = Type3('u32', TypeApplication_Nullary(None, None)) -""" -The unsigned 32-bit integer type. - -Operations on variables employ modular arithmetic, with modulus 2^32. -""" - -u64 = Type3('u64', TypeApplication_Nullary(None, None)) -""" -The unsigned 64-bit integer type. - -Operations on variables employ modular arithmetic, with modulus 2^64. -""" - -i8 = Type3('i8', TypeApplication_Nullary(None, None)) -""" -The signed 8-bit integer type. - -Operations on variables employ modular arithmetic, with modulus 2^8, but -with the middel point being 0. -""" - -i16 = Type3('i16', TypeApplication_Nullary(None, None)) -""" -The signed 16-bit integer type. - -Operations on variables employ modular arithmetic, with modulus 2^16, but -with the middel point being 0. -""" - -i32 = Type3('i32', TypeApplication_Nullary(None, None)) -""" -The unsigned 32-bit integer type. - -Operations on variables employ modular arithmetic, with modulus 2^32, but -with the middel point being 0. -""" - -i64 = Type3('i64', TypeApplication_Nullary(None, None)) -""" -The unsigned 64-bit integer type. - -Operations on variables employ modular arithmetic, with modulus 2^64, but -with the middel point being 0. -""" - -f32 = Type3('f32', TypeApplication_Nullary(None, None)) -""" -A 32-bits IEEE 754 float, of 32 bits width. -""" - -f64 = Type3('f64', TypeApplication_Nullary(None, None)) -""" -A 32-bits IEEE 754 float, of 64 bits width. -""" - -dynamic_array = TypeConstructor_DynamicArray('dynamic_array') -""" -This is a dynamic length piece of memory. - -It should be applied with two arguments. It has a runtime -determined length, and each argument is the same. -""" - -static_array = TypeConstructor_StaticArray('static_array') -""" -This is a fixed length piece of memory. - -It should be applied with two arguments. It has a compile time -determined length, and each argument is the same. -""" - -tuple_ = TypeConstructor_Tuple('tuple') -""" -This is a fixed length piece of memory. - -It should be applied with zero or more arguments. It has a compile time -determined length, and each argument can be different. -""" - -function = TypeConstructor_Function('function') -""" -This is a function. - -It should be applied with one or more arguments. The last argument is the 'return' type. -""" - -struct = TypeConstructor_Struct('struct') -""" -This is like a tuple, but each argument is named, so that developers -can get and set fields by name. -""" - -a = TypeVariable('a', TypeVariableApplication_Nullary(None, None)) -b = TypeVariable('b', TypeVariableApplication_Nullary(None, None)) - -t = TypeConstructorVariable('t') - -Eq = Type3Class('Eq', (a, ), methods={}, operators={ - '==': [a, a, bool_], - '!=': [a, a, bool_], - # FIXME: Do we want to expose 'eqz'? Or is that a compiler optimization? -}) - -instance_type_class(Eq, u8, operators={ - '==': stdtypes.u8_eq_equals, - '!=': stdtypes.u8_eq_not_equals, -}) -instance_type_class(Eq, u16, operators={ - '==': stdtypes.u16_eq_equals, - '!=': stdtypes.u16_eq_not_equals, -}) -instance_type_class(Eq, u32, operators={ - '==': stdtypes.u32_eq_equals, - '!=': stdtypes.u32_eq_not_equals, -}) -instance_type_class(Eq, u64, operators={ - '==': stdtypes.u64_eq_equals, - '!=': stdtypes.u64_eq_not_equals, -}) -instance_type_class(Eq, i8, operators={ - '==': stdtypes.i8_eq_equals, - '!=': stdtypes.i8_eq_not_equals, -}) -instance_type_class(Eq, i16, operators={ - '==': stdtypes.i16_eq_equals, - '!=': stdtypes.i16_eq_not_equals, -}) -instance_type_class(Eq, i32, operators={ - '==': stdtypes.i32_eq_equals, - '!=': stdtypes.i32_eq_not_equals, -}) -instance_type_class(Eq, i64, operators={ - '==': stdtypes.i64_eq_equals, - '!=': stdtypes.i64_eq_not_equals, -}) -instance_type_class(Eq, f32, operators={ - '==': stdtypes.f32_eq_equals, - '!=': stdtypes.f32_eq_not_equals, -}) -instance_type_class(Eq, f64, operators={ - '==': stdtypes.f64_eq_equals, - '!=': stdtypes.f64_eq_not_equals, -}) - -Ord = Type3Class('Ord', (a, ), methods={ - 'min': [a, a, a], - 'max': [a, a, a], -}, operators={ - '<': [a, a, bool_], - '<=': [a, a, bool_], - '>': [a, a, bool_], - '>=': [a, a, bool_], -}, inherited_classes=[Eq]) - -instance_type_class(Ord, u8, methods={ - 'min': stdtypes.u8_ord_min, - 'max': stdtypes.u8_ord_max, -}, operators={ - '<': stdtypes.u8_ord_less_than, - '<=': stdtypes.u8_ord_less_than_or_equal, - '>': stdtypes.u8_ord_greater_than, - '>=': stdtypes.u8_ord_greater_than_or_equal, -}) -instance_type_class(Ord, u16, methods={ - 'min': stdtypes.u16_ord_min, - 'max': stdtypes.u16_ord_max, -}, operators={ - '<': stdtypes.u16_ord_less_than, - '<=': stdtypes.u16_ord_less_than_or_equal, - '>': stdtypes.u16_ord_greater_than, - '>=': stdtypes.u16_ord_greater_than_or_equal, -}) -instance_type_class(Ord, u32, methods={ - 'min': stdtypes.u32_ord_min, - 'max': stdtypes.u32_ord_max, -}, operators={ - '<': stdtypes.u32_ord_less_than, - '<=': stdtypes.u32_ord_less_than_or_equal, - '>': stdtypes.u32_ord_greater_than, - '>=': stdtypes.u32_ord_greater_than_or_equal, -}) -instance_type_class(Ord, u64, methods={ - 'min': stdtypes.u64_ord_min, - 'max': stdtypes.u64_ord_max, -}, operators={ - '<': stdtypes.u64_ord_less_than, - '<=': stdtypes.u64_ord_less_than_or_equal, - '>': stdtypes.u64_ord_greater_than, - '>=': stdtypes.u64_ord_greater_than_or_equal, -}) -instance_type_class(Ord, i8, methods={ - 'min': stdtypes.i8_ord_min, - 'max': stdtypes.i8_ord_max, -}, operators={ - '<': stdtypes.i8_ord_less_than, - '<=': stdtypes.i8_ord_less_than_or_equal, - '>': stdtypes.i8_ord_greater_than, - '>=': stdtypes.i8_ord_greater_than_or_equal, -}) -instance_type_class(Ord, i16, methods={ - 'min': stdtypes.i16_ord_min, - 'max': stdtypes.i16_ord_max, -}, operators={ - '<': stdtypes.i16_ord_less_than, - '<=': stdtypes.i16_ord_less_than_or_equal, - '>': stdtypes.i16_ord_greater_than, - '>=': stdtypes.i16_ord_greater_than_or_equal, -}) -instance_type_class(Ord, i32, methods={ - 'min': stdtypes.i32_ord_min, - 'max': stdtypes.i32_ord_max, -}, operators={ - '<': stdtypes.i32_ord_less_than, - '<=': stdtypes.i32_ord_less_than_or_equal, - '>': stdtypes.i32_ord_greater_than, - '>=': stdtypes.i32_ord_greater_than_or_equal, -}) -instance_type_class(Ord, i64, methods={ - 'min': stdtypes.i64_ord_min, - 'max': stdtypes.i64_ord_max, -}, operators={ - '<': stdtypes.i64_ord_less_than, - '<=': stdtypes.i64_ord_less_than_or_equal, - '>': stdtypes.i64_ord_greater_than, - '>=': stdtypes.i64_ord_greater_than_or_equal, -}) -instance_type_class(Ord, f32, methods={ - 'min': stdtypes.f32_ord_min, - 'max': stdtypes.f32_ord_max, -}, operators={ - '<': stdtypes.f32_ord_less_than, - '<=': stdtypes.f32_ord_less_than_or_equal, - '>': stdtypes.f32_ord_greater_than, - '>=': stdtypes.f32_ord_greater_than_or_equal, -}) -instance_type_class(Ord, f64, methods={ - 'min': stdtypes.f64_ord_min, - 'max': stdtypes.f64_ord_max, -}, operators={ - '<': stdtypes.f64_ord_less_than, - '<=': stdtypes.f64_ord_less_than_or_equal, - '>': stdtypes.f64_ord_greater_than, - '>=': stdtypes.f64_ord_greater_than_or_equal, -}) - -Bits = Type3Class('Bits', (a, ), methods={ - 'shl': [a, u32, a], # Logical shift left - 'shr': [a, u32, a], # Logical shift right - 'rotl': [a, u32, a], # Rotate bits left - 'rotr': [a, u32, a], # Rotate bits right - # FIXME: Do we want to expose clz, ctz, popcnt? -}, operators={ - '&': [a, a, a], # Bit-wise and - '|': [a, a, a], # Bit-wise or - '^': [a, a, a], # Bit-wise xor -}) - -instance_type_class(Bits, u8, methods={ - 'shl': stdtypes.u8_bits_logical_shift_left, - 'shr': stdtypes.u8_bits_logical_shift_right, - 'rotl': stdtypes.u8_bits_rotate_left, - 'rotr': stdtypes.u8_bits_rotate_right, -}, operators={ - '&': stdtypes.u8_bits_bitwise_and, - '|': stdtypes.u8_bits_bitwise_or, - '^': stdtypes.u8_bits_bitwise_xor, -}) -instance_type_class(Bits, u16, methods={ - 'shl': stdtypes.u16_bits_logical_shift_left, - 'shr': stdtypes.u16_bits_logical_shift_right, - 'rotl': stdtypes.u16_bits_rotate_left, - 'rotr': stdtypes.u16_bits_rotate_right, -}, operators={ - '&': stdtypes.u16_bits_bitwise_and, - '|': stdtypes.u16_bits_bitwise_or, - '^': stdtypes.u16_bits_bitwise_xor, -}) -instance_type_class(Bits, u32, methods={ - 'shl': stdtypes.u32_bits_logical_shift_left, - 'shr': stdtypes.u32_bits_logical_shift_right, - 'rotl': stdtypes.u32_bits_rotate_left, - 'rotr': stdtypes.u32_bits_rotate_right, -}, operators={ - '&': stdtypes.u32_bits_bitwise_and, - '|': stdtypes.u32_bits_bitwise_or, - '^': stdtypes.u32_bits_bitwise_xor, -}) -instance_type_class(Bits, u64, methods={ - 'shl': stdtypes.u64_bits_logical_shift_left, - 'shr': stdtypes.u64_bits_logical_shift_right, - 'rotl': stdtypes.u64_bits_rotate_left, - 'rotr': stdtypes.u64_bits_rotate_right, -}, operators={ - '&': stdtypes.u64_bits_bitwise_and, - '|': stdtypes.u64_bits_bitwise_or, - '^': stdtypes.u64_bits_bitwise_xor, -}) - -NatNum = Type3Class('NatNum', (a, ), methods={}, operators={ - '+': [a, a, a], - '-': [a, a, a], - '*': [a, a, a], - '<<': [a, u32, a], # Arithmic shift left - '>>': [a, u32, a], # Arithmic shift right -}) - -instance_type_class(NatNum, u32, operators={ - '+': stdtypes.u32_natnum_add, - '-': stdtypes.u32_natnum_sub, - '*': stdtypes.u32_natnum_mul, - '<<': stdtypes.u32_natnum_arithmic_shift_left, - '>>': stdtypes.u32_natnum_arithmic_shift_right, -}) -instance_type_class(NatNum, u64, operators={ - '+': stdtypes.u64_natnum_add, - '-': stdtypes.u64_natnum_sub, - '*': stdtypes.u64_natnum_mul, - '<<': stdtypes.u64_natnum_arithmic_shift_left, - '>>': stdtypes.u64_natnum_arithmic_shift_right, -}) -instance_type_class(NatNum, i32, operators={ - '+': stdtypes.i32_natnum_add, - '-': stdtypes.i32_natnum_sub, - '*': stdtypes.i32_natnum_mul, - '<<': stdtypes.i32_natnum_arithmic_shift_left, - '>>': stdtypes.i32_natnum_arithmic_shift_right, -}) -instance_type_class(NatNum, i64, operators={ - '+': stdtypes.i64_natnum_add, - '-': stdtypes.i64_natnum_sub, - '*': stdtypes.i64_natnum_mul, - '<<': stdtypes.i64_natnum_arithmic_shift_left, - '>>': stdtypes.i64_natnum_arithmic_shift_right, -}) -instance_type_class(NatNum, f32, operators={ - '+': stdtypes.f32_natnum_add, - '-': stdtypes.f32_natnum_sub, - '*': stdtypes.f32_natnum_mul, - '<<': stdtypes.f32_natnum_arithmic_shift_left, - '>>': stdtypes.f32_natnum_arithmic_shift_right, -}) -instance_type_class(NatNum, f64, operators={ - '+': stdtypes.f64_natnum_add, - '-': stdtypes.f64_natnum_sub, - '*': stdtypes.f64_natnum_mul, - '<<': stdtypes.f64_natnum_arithmic_shift_left, - '>>': stdtypes.f64_natnum_arithmic_shift_right, -}) - -IntNum = Type3Class('IntNum', (a, ), methods={ - 'abs': [a, a], - 'neg': [a, a], -}, operators={}, inherited_classes=[NatNum]) - -instance_type_class(IntNum, i32, methods={ - 'abs': stdtypes.i32_intnum_abs, - 'neg': stdtypes.i32_intnum_neg, -}) -instance_type_class(IntNum, i64, methods={ - 'abs': stdtypes.i64_intnum_abs, - 'neg': stdtypes.i64_intnum_neg, -}) -instance_type_class(IntNum, f32, methods={ - 'abs': stdtypes.f32_intnum_abs, - 'neg': stdtypes.f32_intnum_neg, -}) -instance_type_class(IntNum, f64, methods={ - 'abs': stdtypes.f64_intnum_abs, - 'neg': stdtypes.f64_intnum_neg, -}) - -Integral = Type3Class('Integral', (a, ), methods={ -}, operators={ - '//': [a, a, a], - '%': [a, a, a], -}, inherited_classes=[NatNum]) - -instance_type_class(Integral, u32, operators={ - '//': stdtypes.u32_integral_div, - '%': stdtypes.u32_integral_rem, -}) -instance_type_class(Integral, u64, operators={ - '//': stdtypes.u64_integral_div, - '%': stdtypes.u64_integral_rem, -}) -instance_type_class(Integral, i32, operators={ - '//': stdtypes.i32_integral_div, - '%': stdtypes.i32_integral_rem, -}) -instance_type_class(Integral, i64, operators={ - '//': stdtypes.i64_integral_div, - '%': stdtypes.i64_integral_rem, -}) - -Fractional = Type3Class('Fractional', (a, ), methods={ - 'ceil': [a, a], - 'floor': [a, a], - 'trunc': [a, a], - 'nearest': [a, a], -}, operators={ - '/': [a, a, a], -}, inherited_classes=[NatNum]) - -instance_type_class(Fractional, f32, methods={ - 'ceil': stdtypes.f32_fractional_ceil, - 'floor': stdtypes.f32_fractional_floor, - 'trunc': stdtypes.f32_fractional_trunc, - 'nearest': stdtypes.f32_fractional_nearest, -}, operators={ - '/': stdtypes.f32_fractional_div, -}) -instance_type_class(Fractional, f64, methods={ - 'ceil': stdtypes.f64_fractional_ceil, - 'floor': stdtypes.f64_fractional_floor, - 'trunc': stdtypes.f64_fractional_trunc, - 'nearest': stdtypes.f64_fractional_nearest, -}, operators={ - '/': stdtypes.f64_fractional_div, -}) - -Floating = Type3Class('Floating', (a, ), methods={ - 'sqrt': [a, a], -}, operators={}, inherited_classes=[Fractional]) - -# FIXME: Do we want to expose copysign? - -instance_type_class(Floating, f32, methods={ - 'sqrt': stdtypes.f32_floating_sqrt, -}) -instance_type_class(Floating, f64, methods={ - 'sqrt': stdtypes.f64_floating_sqrt, -}) - -Sized_ = Type3Class('Sized', (t, ), methods={ - 'len': [t(a), u32], -}, operators={}) # FIXME: Once we get type class families, add [] here - -instance_type_class(Sized_, dynamic_array, methods={ - 'len': stdtypes.dynamic_array_sized_len, -}) -instance_type_class(Sized_, static_array, methods={ - 'len': stdtypes.static_array_sized_len, -}) - -Extendable = Type3Class('Extendable', (a, b, ), methods={ - 'extend': [a, b], - 'wrap': [b, a], -}, operators={}) - -instance_type_class(Extendable, u8, u16, methods={ - 'extend': stdtypes.u8_u16_extend, - 'wrap': stdtypes.u8_u16_wrap, -}) -instance_type_class(Extendable, u8, u32, methods={ - 'extend': stdtypes.u8_u32_extend, - 'wrap': stdtypes.u8_u32_wrap, -}) -instance_type_class(Extendable, u8, u64, methods={ - 'extend': stdtypes.u8_u64_extend, - 'wrap': stdtypes.u8_u64_wrap, -}) -instance_type_class(Extendable, u16, u32, methods={ - 'extend': stdtypes.u16_u32_extend, - 'wrap': stdtypes.u16_u32_wrap, -}) -instance_type_class(Extendable, u16, u64, methods={ - 'extend': stdtypes.u16_u64_extend, - 'wrap': stdtypes.u16_u64_wrap, -}) -instance_type_class(Extendable, u32, u64, methods={ - 'extend': stdtypes.u32_u64_extend, - 'wrap': stdtypes.u32_u64_wrap, -}) -instance_type_class(Extendable, i8, i16, methods={ - 'extend': stdtypes.i8_i16_extend, - 'wrap': stdtypes.i8_i16_wrap, -}) -instance_type_class(Extendable, i8, i32, methods={ - 'extend': stdtypes.i8_i32_extend, - 'wrap': stdtypes.i8_i32_wrap, -}) -instance_type_class(Extendable, i8, i64, methods={ - 'extend': stdtypes.i8_i64_extend, - 'wrap': stdtypes.i8_i64_wrap, -}) -instance_type_class(Extendable, i16, i32, methods={ - 'extend': stdtypes.i16_i32_extend, - 'wrap': stdtypes.i16_i32_wrap, -}) -instance_type_class(Extendable, i16, i64, methods={ - 'extend': stdtypes.i16_i64_extend, - 'wrap': stdtypes.i16_i64_wrap, -}) -instance_type_class(Extendable, i32, i64, methods={ - 'extend': stdtypes.i32_i64_extend, - 'wrap': stdtypes.i32_i64_wrap, -}) - -Promotable = Type3Class('Promotable', (a, b, ), methods={ - 'promote': [a, b], - 'demote': [b, a], -}, operators={}) - -instance_type_class(Promotable, f32, f64, methods={ - 'promote': stdtypes.f32_f64_promote, - 'demote': stdtypes.f32_f64_demote, -}) - -Reinterpretable = Type3Class('Reinterpretable', (a, b, ), methods={ - 'reinterpret': [a, b] -}, operators={}) - -instance_type_class(Reinterpretable, u32, f32, methods={ - 'reinterpret': stdtypes.u32_f32_reinterpret, -}) -instance_type_class(Reinterpretable, u64, f64, methods={ - 'reinterpret': stdtypes.u64_f64_reinterpret, -}) -instance_type_class(Reinterpretable, i32, f32, methods={ - 'reinterpret': stdtypes.i32_f32_reinterpret, -}) -instance_type_class(Reinterpretable, i64, f64, methods={ - 'reinterpret': stdtypes.i64_f64_reinterpret, -}) -instance_type_class(Reinterpretable, f32, u32, methods={ - 'reinterpret': stdtypes.f32_u32_reinterpret, -}) -instance_type_class(Reinterpretable, f64, u64, methods={ - 'reinterpret': stdtypes.f64_u64_reinterpret, -}) -instance_type_class(Reinterpretable, f32, i32, methods={ - 'reinterpret': stdtypes.f32_i32_reinterpret, -}) -instance_type_class(Reinterpretable, f64, i64, methods={ - 'reinterpret': stdtypes.f64_i64_reinterpret, -}) - -Convertable = Type3Class('Convertable', (a, b, ), methods={ - 'convert': [a, b], - 'truncate': [b, a], # To prevent name clas with Fractional -}, operators={}) - -instance_type_class(Convertable, u32, f32, methods={ - 'convert': stdtypes.u32_f32_convert, - 'truncate': stdtypes.u32_f32_truncate, -}) -instance_type_class(Convertable, u32, f64, methods={ - 'convert': stdtypes.u32_f64_convert, - 'truncate': stdtypes.u32_f64_truncate, -}) -instance_type_class(Convertable, u64, f32, methods={ - 'convert': stdtypes.u64_f32_convert, - 'truncate': stdtypes.u64_f32_truncate, -}) -instance_type_class(Convertable, u64, f64, methods={ - 'convert': stdtypes.u64_f64_convert, - 'truncate': stdtypes.u64_f64_truncate, -}) -instance_type_class(Convertable, i32, f32, methods={ - 'convert': stdtypes.i32_f32_convert, - 'truncate': stdtypes.i32_f32_truncate, -}) -instance_type_class(Convertable, i32, f64, methods={ - 'convert': stdtypes.i32_f64_convert, - 'truncate': stdtypes.i32_f64_truncate, -}) -instance_type_class(Convertable, i64, f32, methods={ - 'convert': stdtypes.i64_f32_convert, - 'truncate': stdtypes.i64_f32_truncate, -}) -instance_type_class(Convertable, i64, f64, methods={ - 'convert': stdtypes.i64_f64_convert, - 'truncate': stdtypes.i64_f64_truncate, -}) - - -Foldable = Type3Class('Foldable', (t, ), methods={ - 'sum': [t(a), a], - 'foldl': [[b, a, b], b, t(a), b], - 'foldr': [[a, b, b], b, t(a), b], -}, operators={}, additional_context={ - 'sum': [Constraint_TypeClassInstanceExists(NatNum, (a, ))], -}) - -instance_type_class(Foldable, dynamic_array, methods={ - 'sum': stdtypes.dynamic_array_sum, - 'foldl': stdtypes.dynamic_array_foldl, - 'foldr': stdtypes.dynamic_array_foldr, -}) -instance_type_class(Foldable, static_array, methods={ - 'sum': stdtypes.static_array_sum, - 'foldl': stdtypes.static_array_foldl, - 'foldr': stdtypes.static_array_foldr, -}) - -bytes_ = dynamic_array(u8) - -PRELUDE_TYPES: dict[str, Type3] = { - 'none': none, - 'bool': bool_, - 'u8': u8, - 'u16': u16, - 'u32': u32, - 'u64': u64, - 'i8': i8, - 'i16': i16, - 'i32': i32, - 'i64': i64, - 'f32': f32, - 'f64': f64, - 'bytes': bytes_, -} - -PRELUDE_TYPE_CLASSES = { - 'Eq': Eq, - 'Ord': Ord, - 'Bits': Bits, - 'NatNum': NatNum, - 'IntNum': IntNum, - 'Integral': Integral, - 'Fractional': Fractional, - 'Floating': Floating, - 'Extendable': Extendable, - 'Promotable': Promotable, -} - -PRELUDE_OPERATORS = { - **Bits.operators, - **Eq.operators, - **Ord.operators, - **Fractional.operators, - **Integral.operators, - **IntNum.operators, - **NatNum.operators, -} - -PRELUDE_METHODS = { - **Bits.methods, - **Eq.methods, - **Ord.methods, - **Floating.methods, - **Fractional.methods, - **Integral.methods, - **IntNum.methods, - **NatNum.methods, - **Sized_.methods, - **Extendable.methods, - **Promotable.methods, - **Reinterpretable.methods, - **Convertable.methods, - **Foldable.methods, -} diff --git a/phasm/runtime.py b/phasm/runtime.py deleted file mode 100644 index d456971..0000000 --- a/phasm/runtime.py +++ /dev/null @@ -1,62 +0,0 @@ -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 TYPE_INFO_CONSTRUCTED.alloc_size - - sa_type, sa_len = args - - return sa_len.value * calculate_alloc_size(sa_type, is_member=True) - -def calculate_alloc_size_tuple(is_member: bool, args: tuple[Type3, ...]) -> int: - if is_member: - return TYPE_INFO_CONSTRUCTED.alloc_size - - return sum( - calculate_alloc_size(x, is_member=True) - for x in args - ) - -def calculate_alloc_size_struct(is_member: bool, args: tuple[tuple[str, Type3], ...]) -> int: - if is_member: - return TYPE_INFO_CONSTRUCTED.alloc_size - - return sum( - calculate_alloc_size(x, is_member=True) - for _, x in args - ) - -ALLOC_SIZE_ROUTER = TypeApplicationRouter[bool, int]() -ALLOC_SIZE_ROUTER.add(prelude.static_array, calculate_alloc_size_static_array) -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: - 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) - except NoRouteForTypeException: - if is_member: - # By default, 'boxed' or 'constructed' types are - # stored as pointers when a member of a struct or tuple - return TYPE_INFO_CONSTRUCTED.alloc_size - - raise NotImplementedError(typ) - -def calculate_member_offset(st_name: str, st_args: tuple[tuple[str, Type3], ...], needle: str) -> int: - result = 0 - - for memnam, memtyp in st_args: - if needle == memnam: - return result - - result += calculate_alloc_size(memtyp, is_member=True) - - raise Exception(f'{needle} not in {st_name}') diff --git a/phasm/stdlib/types.py b/phasm/stdlib/types.py index 38c5c98..5603c5c 100644 --- a/phasm/stdlib/types.py +++ b/phasm/stdlib/types.py @@ -1,13 +1,13 @@ """ stdlib: Standard types that are not wasm primitives """ -from typing import Mapping, NamedTuple, Type +from typing import Mapping +from phasm.build.base import TypeInfo 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, @@ -18,38 +18,22 @@ 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), + 'none': TypeInfo('none', WasmTypeNone, 'nop', 'nop', 0, None), + 'bool': TypeInfo('bool', WasmTypeInt32, 'i32.load8_u', 'i32.store8', 1, None), - 'u8': TypeInfo('u8', WasmTypeInt32, 'i32.load8_u', 'i32.store8', 1), - 'i8': TypeInfo('i8', WasmTypeInt32, 'i32.load8_s', 'i32.store8', 1), - 'u16': TypeInfo('u16', WasmTypeInt32, 'i32.load16_u', 'i32.store16', 2), - 'i16': TypeInfo('i16', WasmTypeInt32, 'i32.load16_s', 'i32.store16', 2), + 'u8': TypeInfo('u8', WasmTypeInt32, 'i32.load8_u', 'i32.store8', 1, False), + 'i8': TypeInfo('i8', WasmTypeInt32, 'i32.load8_s', 'i32.store8', 1, True), + 'u16': TypeInfo('u16', WasmTypeInt32, 'i32.load16_u', 'i32.store16', 2, False), + 'i16': TypeInfo('i16', WasmTypeInt32, 'i32.load16_s', 'i32.store16', 2, True), - '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), + 'u32': TypeInfo('u32', WasmTypeInt32, 'i32.load', 'i32.store', 4, False), + 'u64': TypeInfo('u64', WasmTypeInt64, 'i64.load', 'i64.store', 8, False), + 'i32': TypeInfo('i32', WasmTypeInt32, 'i32.load', 'i32.store', 4, True), + 'i64': TypeInfo('i64', WasmTypeInt64, 'i64.load', 'i64.store', 8, True), + 'f32': TypeInfo('f32', WasmTypeFloat32, 'f32.load', 'f32.store', 4, None), + 'f64': TypeInfo('f64', WasmTypeFloat64, 'f64.load', 'f64.store', 8, None), + 'ptr': TypeInfo('ptr', WasmTypeInt32, 'i32.load', 'i32.store', 4, None), } # By default, constructed types are passed as pointers @@ -57,7 +41,7 @@ TYPE_INFO_MAP: Mapping[str, TypeInfo] = { # 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) +TYPE_INFO_CONSTRUCTED = TYPE_INFO_MAP['ptr'] @func_wrapper(exported=True) def __alloc_bytes__(g: Generator, length: i32) -> i32: diff --git a/phasm/type3/constraints.py b/phasm/type3/constraints.py index cd1175c..8238837 100644 --- a/phasm/type3/constraints.py +++ b/phasm/type3/constraints.py @@ -5,7 +5,8 @@ These need to be resolved before the program can be compiled. """ from typing import Any, Dict, Iterable, List, Optional, Tuple, Union -from .. import ourlang, prelude +from .. import ourlang +from ..build import builtins from .functions import FunctionArgument, TypeVariable from .placeholders import PlaceholderForType, Type3OrPlaceholder from .routers import NoRouteForTypeException, TypeApplicationRouter @@ -271,7 +272,7 @@ class SameFunctionArgumentConstraint(ConstraintBase): return [ SameTypeConstraint( typ, - prelude.function(*exp_type_arg_list), + builtins.function(*exp_type_arg_list), comment=self.comment, ) ] @@ -326,9 +327,9 @@ class TupleMatchConstraint(ConstraintBase): ] GENERATE_ROUTER = TypeApplicationRouter['TupleMatchConstraint', CheckResult]() - GENERATE_ROUTER.add(prelude.dynamic_array, _generate_dynamic_array) - GENERATE_ROUTER.add(prelude.static_array, _generate_static_array) - GENERATE_ROUTER.add(prelude.tuple_, _generate_tuple) + GENERATE_ROUTER.add(builtins.dynamic_array, _generate_dynamic_array) + GENERATE_ROUTER.add(builtins.static_array, _generate_static_array) + GENERATE_ROUTER.add(builtins.tuple_, _generate_tuple) def check(self) -> CheckResult: exp_type = self.exp_type @@ -427,11 +428,17 @@ class LiteralFitsConstraint(ConstraintBase): self.literal = literal def _generate_dynamic_array(self, da_args: tuple[Type3]) -> CheckResult: + da_type, = da_args + + if da_type.name == 'u8': + if not isinstance(self.literal, ourlang.ConstantBytes): + return Error('Must be bytes', comment=self.comment) + + return None + if not isinstance(self.literal, ourlang.ConstantTuple): return Error('Must be tuple', comment=self.comment) - da_type, = da_args - res: list[ConstraintBase] = [] res.extend( @@ -530,10 +537,10 @@ class LiteralFitsConstraint(ConstraintBase): return res GENERATE_ROUTER = TypeApplicationRouter['LiteralFitsConstraint', CheckResult]() - GENERATE_ROUTER.add(prelude.dynamic_array, _generate_dynamic_array) - GENERATE_ROUTER.add(prelude.static_array, _generate_static_array) - GENERATE_ROUTER.add(prelude.struct, _generate_struct) - GENERATE_ROUTER.add(prelude.tuple_, _generate_tuple) + GENERATE_ROUTER.add(builtins.dynamic_array, _generate_dynamic_array) + GENERATE_ROUTER.add(builtins.static_array, _generate_static_array) + GENERATE_ROUTER.add(builtins.struct, _generate_struct) + GENERATE_ROUTER.add(builtins.tuple_, _generate_tuple) def check(self) -> CheckResult: int_table: Dict[str, Tuple[int, bool]] = { @@ -581,12 +588,6 @@ class LiteralFitsConstraint(ConstraintBase): return Error('Must be real', comment=self.comment) # FIXME: Add line information - if self.type3 is prelude.bytes_: - if isinstance(self.literal.value, bytes): - return None - - return Error('Must be bytes', comment=self.comment) # FIXME: Add line information - exp_type = self.type3 try: @@ -666,9 +667,9 @@ class CanBeSubscriptedConstraint(ConstraintBase): ] GENERATE_ROUTER = TypeApplicationRouter['CanBeSubscriptedConstraint', CheckResult]() - GENERATE_ROUTER.add_n(prelude.bytes_, _generate_bytes) - GENERATE_ROUTER.add(prelude.static_array, _generate_static_array) - GENERATE_ROUTER.add(prelude.tuple_, _generate_tuple) + # GENERATE_ROUTER.add_n(builtins.bytes_, _generate_bytes) + GENERATE_ROUTER.add(builtins.static_array, _generate_static_array) + GENERATE_ROUTER.add(builtins.tuple_, _generate_tuple) def check(self) -> CheckResult: if self.type3.resolve_as is None: diff --git a/phasm/type3/constraintsgenerator.py b/phasm/type3/constraintsgenerator.py index f28b88f..9d6a2b0 100644 --- a/phasm/type3/constraintsgenerator.py +++ b/phasm/type3/constraintsgenerator.py @@ -32,7 +32,7 @@ ConstraintGenerator = Generator[ConstraintBase, None, None] def phasm_type3_generate_constraints(inp: ourlang.Module) -> List[ConstraintBase]: ctx = Context() - ctx.type_class_instances_existing.update(prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING) + ctx.type_class_instances_existing.update(inp.build.type_class_instances) return [*module(ctx, inp)] diff --git a/phasm/type3/types.py b/phasm/type3/types.py index e6bc4f2..9406c1b 100644 --- a/phasm/type3/types.py +++ b/phasm/type3/types.py @@ -79,7 +79,7 @@ class Type3(KindArgument): def __eq__(self, other: Any) -> bool: if not isinstance(other, Type3): - raise NotImplementedError + raise NotImplementedError(other) return self is other diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py index 192ee21..caa4df2 100644 --- a/tests/integration/helpers.py +++ b/tests/integration/helpers.py @@ -4,16 +4,16 @@ import sys from typing import Any, Callable, Generator, Iterable, List, TextIO, Union from phasm import compiler, prelude +from phasm.build import builtins from phasm.codestyle import phasm_render -from phasm.runtime import ( - calculate_alloc_size, - calculate_alloc_size_static_array, - 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 +from phasm.wasm import ( + WasmTypeFloat32, + WasmTypeFloat64, + WasmTypeInt32, + WasmTypeInt64, +) from . import runners @@ -125,17 +125,14 @@ class Suite: runner.interpreter_dump_memory(sys.stderr) for arg, arg_typ in zip(args, func_args, strict=True): - if arg_typ in (prelude.u8, prelude.u16, prelude.u32, prelude.u64, ): + arg_typ_info = runner.phasm_ast.build.type_info_map.get(arg_typ.name) + + if arg_typ_info and (arg_typ_info.wasm_type is WasmTypeInt32 or arg_typ_info.wasm_type is WasmTypeInt64): assert isinstance(arg, int) wasm_args.append(arg) continue - if arg_typ in (prelude.i8, prelude.i16, prelude.i32, prelude.i64, ): - assert isinstance(arg, int) - wasm_args.append(arg) - continue - - if arg_typ in (prelude.f32, prelude.f64, ): + if arg_typ_info and (arg_typ_info.wasm_type is WasmTypeFloat32 or arg_typ_info.wasm_type is WasmTypeFloat64): assert isinstance(arg, float) wasm_args.append(arg) continue @@ -168,19 +165,6 @@ class Suite: def write_header(textio: TextIO, msg: str) -> None: textio.write(f'{DASHES} {msg.ljust(16)} {DASHES}\n') -WRITE_LOOKUP_MAP = { - 'u8': compiler.module_data_u8, - 'u16': compiler.module_data_u16, - 'u32': compiler.module_data_u32, - 'u64': compiler.module_data_u64, - 'i8': compiler.module_data_i8, - 'i16': compiler.module_data_i16, - 'i32': compiler.module_data_i32, - 'i64': compiler.module_data_i64, - 'f32': compiler.module_data_f32, - 'f64': compiler.module_data_f64, -} - def _write_memory_stored_value( runner: runners.RunnerBase, adr: int, @@ -189,10 +173,14 @@ def _write_memory_stored_value( ) -> int: try: adr2 = ALLOCATE_MEMORY_STORED_ROUTER((runner, val), val_typ) - runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2)) - return TYPE_INFO_CONSTRUCTED.alloc_size + + ptr_type_info = runner.phasm_ast.build.type_info_map['ptr'] + to_write = compiler.module_data_primitive(ptr_type_info, adr2) + runner.interpreter_write_memory(adr, to_write) + return runner.phasm_ast.build.type_info_constructed.alloc_size except NoRouteForTypeException: - to_write = WRITE_LOOKUP_MAP[val_typ.name](val) + val_typ_info = runner.phasm_ast.build.type_info_map[val_typ.name] + to_write = compiler.module_data_primitive(val_typ_info, val) runner.interpreter_write_memory(adr, to_write) return len(to_write) @@ -213,16 +201,19 @@ def _allocate_memory_stored_dynamic_array(attrs: tuple[runners.RunnerBase, Any], da_type, = da_args + if da_type.name == 'u8': + return _allocate_memory_stored_bytes(attrs) + if not isinstance(val, tuple): raise InvalidArgumentException(f'Expected tuple; got {val!r} instead') - alloc_size = 4 + len(val) * calculate_alloc_size(da_type, True) + alloc_size = 4 + len(val) * runner.phasm_ast.build.calculate_alloc_size(da_type, True) adr = runner.call('stdlib.alloc.__alloc__', alloc_size) assert isinstance(adr, int) # Type int sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n') offset = adr - offset += _write_memory_stored_value(runner, offset, prelude.u32, len(val)) + offset += _write_memory_stored_value(runner, offset, runner.phasm_ast.build.types['u32'], len(val)) for val_el_val in val: offset += _write_memory_stored_value(runner, offset, da_type, val_el_val) return adr @@ -237,7 +228,7 @@ def _allocate_memory_stored_static_array(attrs: tuple[runners.RunnerBase, Any], if sa_len.value != len(val): raise InvalidArgumentException(f'Expected tuple of length {sa_len.value}; got {val!r} instead') - alloc_size = calculate_alloc_size_static_array(False, sa_args) + alloc_size = runner.phasm_ast.build.calculate_alloc_size_static_array(sa_args) adr = runner.call('stdlib.alloc.__alloc__', alloc_size) assert isinstance(adr, int) # Type int sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n') @@ -252,7 +243,7 @@ def _allocate_memory_stored_struct(attrs: tuple[runners.RunnerBase, Any], st_arg assert isinstance(val, dict) - alloc_size = calculate_alloc_size_struct(False, st_args) + alloc_size = runner.phasm_ast.build.calculate_alloc_size_struct(st_args) adr = runner.call('stdlib.alloc.__alloc__', alloc_size) assert isinstance(adr, int) sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n') @@ -272,7 +263,7 @@ def _allocate_memory_stored_tuple(attrs: tuple[runners.RunnerBase, Any], tp_args assert isinstance(val, tuple) - alloc_size = calculate_alloc_size_tuple(False, tp_args) + alloc_size = runner.phasm_ast.build.calculate_alloc_size_tuple(tp_args) adr = runner.call('stdlib.alloc.__alloc__', alloc_size) assert isinstance(adr, int) sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n') @@ -285,11 +276,11 @@ def _allocate_memory_stored_tuple(attrs: tuple[runners.RunnerBase, Any], tp_args return adr ALLOCATE_MEMORY_STORED_ROUTER = TypeApplicationRouter[tuple[runners.RunnerBase, Any], Any]() -ALLOCATE_MEMORY_STORED_ROUTER.add_n(prelude.bytes_, _allocate_memory_stored_bytes) -ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.dynamic_array, _allocate_memory_stored_dynamic_array) -ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.static_array, _allocate_memory_stored_static_array) -ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.struct, _allocate_memory_stored_struct) -ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.tuple_, _allocate_memory_stored_tuple) +# ALLOCATE_MEMORY_STORED_ROUTER.add_n(prelude.bytes_, _allocate_memory_stored_bytes) +ALLOCATE_MEMORY_STORED_ROUTER.add(builtins.dynamic_array, _allocate_memory_stored_dynamic_array) +ALLOCATE_MEMORY_STORED_ROUTER.add(builtins.static_array, _allocate_memory_stored_static_array) +ALLOCATE_MEMORY_STORED_ROUTER.add(builtins.struct, _allocate_memory_stored_struct) +ALLOCATE_MEMORY_STORED_ROUTER.add(builtins.tuple_, _allocate_memory_stored_tuple) def _load_memory_stored_returned_value( runner: runners.RunnerBase, @@ -298,96 +289,105 @@ def _load_memory_stored_returned_value( ) -> Any: ret_type3 = runner.phasm_ast.functions[func_name].returns_type3 - if ret_type3 is prelude.none: - return None + if ret_type3.name in runner.phasm_ast.build.type_info_map: + type_info = runner.phasm_ast.build.type_info_map[ret_type3.name] - if ret_type3 is prelude.bool_: - assert isinstance(wasm_value, int), wasm_value - return 0 != wasm_value + # if ret_type3 is prelude.none: + # return None - if ret_type3 in (prelude.i8, prelude.i16, prelude.i32, prelude.i64): - assert isinstance(wasm_value, int), wasm_value + # if ret_type3 is prelude.bool_: + # assert isinstance(wasm_value, int), wasm_value + # return 0 != wasm_value - if ret_type3 is prelude.i8: - # Values are actually i32 - # Have to reinterpret to load proper value - data = struct.pack(' Any: - typ_info = TYPE_INFO_MAP.get(typ.name, TYPE_INFO_CONSTRUCTED) + typ_info = runner.phasm_ast.build.type_info_map.get(typ.name) - assert len(inp) == typ_info.alloc_size + if typ_info is None: + assert len(inp) == 4 - if typ is prelude.u8: - return struct.unpack(' bytes: @@ -410,13 +410,16 @@ def _load_dynamic_array_from_address(attrs: tuple[runners.RunnerBase, int], da_a runner, adr = attrs da_type, = da_args + if da_type.name == 'u8': + return _load_bytes_from_address(attrs) + sys.stderr.write(f'Reading 0x{adr:08x} {da_type:s}[...]\n') read_bytes = runner.interpreter_read_memory(adr, 4) array_len, = struct.unpack(' bytes: result = Suite(code_py).run_code() - assert b"Hello" == result.returned_value + assert b"Hello" == result.returned_value[0:5] + assert 5 == len(result.returned_value) @pytest.mark.integration_test def test_bytes_export_instantiation():