From 8a027a0b10b7606b948a22dbe02f7215be02a639 Mon Sep 17 00:00:00 2001 From: "Johan B.W. de Vries" Date: Mon, 5 May 2025 14:09:38 +0200 Subject: [PATCH] Implements sum for Foldable types Foldable take a TypeConstructor. The first argument must be a NatNum. --- phasm/compiler.py | 19 +++++-- phasm/prelude/__init__.py | 32 ++++++++++-- phasm/stdlib/types.py | 52 ++++++++++++++++++++ phasm/type3/constraints.py | 4 +- phasm/type3/constraintsgenerator.py | 3 +- phasm/type3/functions.py | 37 +++++++++----- phasm/type3/typeclasses.py | 13 ++--- phasm/type3/types.py | 6 +-- tests/integration/test_lang/test_foldable.py | 41 +++++++++++++++ 9 files changed, 172 insertions(+), 35 deletions(-) create mode 100644 tests/integration/test_lang/test_foldable.py diff --git a/phasm/compiler.py b/phasm/compiler.py index 9903011..d247fff 100644 --- a/phasm/compiler.py +++ b/phasm/compiler.py @@ -2,7 +2,7 @@ This module contains the code to convert parsed Ourlang into WebAssembly code """ import struct -from typing import Dict, List, Optional +from typing import Dict, List, Optional, Union from . import codestyle, ourlang, prelude, wasm from .runtime import calculate_alloc_size, calculate_member_offset @@ -259,6 +259,10 @@ INSTANCES = { prelude.Promotable.methods['demote']: { 'a=f32,b=f64': stdlib_types.f32_f64_demote, }, + prelude.Foldable.methods['sum']: { + 'a=i32,t=i32[4]': stdlib_types.static_array_i32_4_sum, + 'a=i32,t=i32[5]': stdlib_types.static_array_i32_5_sum, + }, } def phasm_compile(inp: ourlang.Module) -> wasm.Module: @@ -457,7 +461,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: assert isinstance(inp.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR - type_var_map: Dict[type3functions.TypeVariable, type3types.Type3] = {} + type_var_map: Dict[Union[type3functions.TypeVariable, type3functions.TypeConstructorVariable], type3types.Type3] = {} for type_var, arg_expr in zip(inp.operator.signature.args, [inp.left, inp.right, inp], strict=True): if not isinstance(type_var, type3functions.TypeVariable): @@ -488,12 +492,16 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: type_var_map = {} for type_var, arg_expr in zip(inp.function.signature.args, inp.arguments + [inp], strict=True): - if not isinstance(type_var, type3functions.TypeVariable): + if isinstance(type_var, type3types.Type3): # Fixed type, not part of the lookup requirements continue - assert isinstance(arg_expr.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR - type_var_map[type_var] = arg_expr.type3 + if isinstance(type_var, (type3functions.TypeVariable, type3functions.TypeConstructorVariable, )): + assert isinstance(arg_expr.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR + type_var_map[type_var] = arg_expr.type3 + continue + + raise NotImplementedError instance_key = ','.join( f'{k.letter}={v.name}' @@ -960,6 +968,7 @@ def module(inp: ourlang.Module) -> wasm.Module: stdlib_types.__u32_pow2__, stdlib_types.__u8_rotl__, stdlib_types.__u8_rotr__, + stdlib_types.__sa_i32_sum__, ] + [ function(x) for x in inp.functions.values() diff --git a/phasm/prelude/__init__.py b/phasm/prelude/__init__.py index 86e42b1..73c416b 100644 --- a/phasm/prelude/__init__.py +++ b/phasm/prelude/__init__.py @@ -1,20 +1,29 @@ """ The prelude are all the builtin types, type classes and methods """ +from typing import Any, Union -from ..type3.functions import TypeVariable +from ..type3.functions import ( + Constraint_TypeClassInstanceExists, + FunctionSignature, + TypeConstructorVariable, + TypeVariable, + TypeVariableContext, +) from ..type3.typeclasses import Type3Class from ..type3.types import ( + IntType3, Type3, + TypeConstructor, TypeConstructor_StaticArray, TypeConstructor_Struct, TypeConstructor_Tuple, ) -PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: set[tuple[Type3Class, tuple[Type3, ...]]] = set() +PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: set[tuple[Type3Class, tuple[Union[Type3, TypeConstructor[Any], TypeConstructor_Struct], ...]]] = set() -def instance_type_class(cls: Type3Class, *typ: Type3) -> None: +def instance_type_class(cls: Type3Class, *typ: Union[Type3, TypeConstructor[Any], TypeConstructor_Struct]) -> None: global PRELUDE_TYPE_CLASS_INSTANCES_EXISTING # TODO: Check for required existing instantiations @@ -93,7 +102,7 @@ bytes_ = Type3('bytes') This is a runtime-determined length piece of memory that can be indexed at runtime. """ -def sa_on_create(typ: Type3) -> None: +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) @@ -106,7 +115,7 @@ It should be applied with one argument. It has a runtime-dynamic length of the same type repeated. """ -def tp_on_create(typ: Type3) -> None: +def tp_on_create(args: tuple[Type3, ...], typ: Type3) -> None: instance_type_class(InternalPassAsPointer, typ) tuple_ = TypeConstructor_Tuple('tuple', on_create=tp_on_create) @@ -143,6 +152,7 @@ PRELUDE_TYPES: dict[str, Type3] = { a = TypeVariable('a') b = TypeVariable('b') +t = TypeConstructorVariable('t', []) InternalPassAsPointer = Type3Class('InternalPassAsPointer', [a], methods={}, operators={}) """ @@ -286,6 +296,17 @@ Promotable = Type3Class('Promotable', [a, b], methods={ instance_type_class(Promotable, f32, f64) +Foldable = Type3Class('Foldable', [t], methods={ + 'sum': FunctionSignature( + TypeVariableContext([ + Constraint_TypeClassInstanceExists(NatNum, [a]), + ]), + [t(a), a] + ), +}, operators={}) + +instance_type_class(Foldable, static_array) + PRELUDE_TYPE_CLASSES = { 'Eq': Eq, 'Ord': Ord, @@ -321,4 +342,5 @@ PRELUDE_METHODS = { **Sized_.methods, **Extendable.methods, **Promotable.methods, + **Foldable.methods, } diff --git a/phasm/stdlib/types.py b/phasm/stdlib/types.py index 5c7e46a..d4edd24 100644 --- a/phasm/stdlib/types.py +++ b/phasm/stdlib/types.py @@ -384,6 +384,50 @@ def __u8_rotr__(g: Generator, x: i32, r: i32) -> i32: return i32('return') # To satisfy mypy +@func_wrapper() +def __sa_i32_sum__(g: Generator, adr: i32, arlen: i32) -> i32: + i32_size = 4 + + s = i32('s') + stop = i32('stop') + + # stop = adr + ar_len * i32_size + g.local.get(adr) + g.local.get(arlen) + g.i32.const(i32_size) + g.i32.mul() + g.i32.add() + g.local.set(stop) + + # sum = 0 + g.i32.const(0) + g.local.set(s) + + with g.loop(): + # sum = sum + *adr + g.local.get(adr) + g.i32.load() + g.local.get(s) + g.i32.add() + g.local.set(s) + + # adr = adr + i32_size + g.local.get(adr) + g.i32.const(i32_size) + g.i32.add() + g.local.tee(adr) + + # loop if adr < stop + g.local.get(stop) + g.i32.lt_u() + g.br_if(0) + + # return sum + g.local.get(s) + g.return_() + + return i32('return') # To satisfy mypy + ## ### ## class Eq @@ -920,3 +964,11 @@ def f32_f64_promote(g: Generator) -> None: def f32_f64_demote(g: Generator) -> None: g.f32.demote_f64() + +def static_array_i32_4_sum(g: Generator) -> None: + g.i32.const(4) + g.add_statement('call $stdlib.types.__sa_i32_sum__') + +def static_array_i32_5_sum(g: Generator) -> None: + g.i32.const(5) + g.add_statement('call $stdlib.types.__sa_i32_sum__') diff --git a/phasm/type3/constraints.py b/phasm/type3/constraints.py index 111fdd3..54fd314 100644 --- a/phasm/type3/constraints.py +++ b/phasm/type3/constraints.py @@ -3,7 +3,7 @@ This module contains possible constraints generated based on the AST These need to be resolved before the program can be compiled. """ -from typing import Dict, List, Optional, Tuple, Union +from typing import Any, Dict, List, Optional, Tuple, Union from .. import ourlang, prelude from . import placeholders, typeclasses, types @@ -48,7 +48,7 @@ class Context: __slots__ = ('type_class_instances_existing', ) # Constraint_TypeClassInstanceExists - type_class_instances_existing: set[tuple[typeclasses.Type3Class, tuple[types.Type3, ...]]] + type_class_instances_existing: set[tuple[typeclasses.Type3Class, tuple[Union[types.Type3, types.TypeConstructor[Any], types.TypeConstructor_Struct], ...]]] def __init__(self) -> None: self.type_class_instances_existing = set() diff --git a/phasm/type3/constraintsgenerator.py b/phasm/type3/constraintsgenerator.py index ba95a86..53d0be3 100644 --- a/phasm/type3/constraintsgenerator.py +++ b/phasm/type3/constraintsgenerator.py @@ -58,6 +58,7 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator: x: placeholders.PlaceholderForType([]) for x in signature.args if isinstance(x, functions.TypeVariable) + or isinstance(x, functions.TypeConstructorVariable) } for call_arg in arguments: @@ -80,7 +81,7 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator: else: comment = f'The type of the value passed to argument {arg_no} of function {func_name} should match the type of that argument' - if isinstance(sig_part, functions.TypeVariable): + if isinstance(sig_part, (functions.TypeVariable, functions.TypeConstructorVariable, )): yield SameTypeConstraint(type_var_map[sig_part], arg_expr.type3, comment=comment) continue diff --git a/phasm/type3/functions.py b/phasm/type3/functions.py index d0147bc..bef75f4 100644 --- a/phasm/type3/functions.py +++ b/phasm/type3/functions.py @@ -34,6 +34,22 @@ class TypeVariable: def __repr__(self) -> str: return f'TypeVariable({repr(self.letter)})' +class TypeConstructorVariable: + """ + Types constructor variable are used in function definition. + + They are a lot like TypeVariable, except that they represent a + type constructor rather than a type directly. + """ + __slots__ = ('letter', 'args', ) + + def __init__(self, letter: str, args: Iterable[TypeVariable]) -> None: + self.letter = letter + self.args = list(args) + + def __call__(self, tvar: TypeVariable) -> 'TypeConstructorVariable': + return TypeConstructorVariable(self.letter, self.args + [tvar]) + class ConstraintBase: __slots__ = () @@ -41,9 +57,9 @@ class Constraint_TypeClassInstanceExists(ConstraintBase): __slots__ = ('type_class3', 'types', ) type_class3: 'Type3Class' - types: list[TypeVariable] + types: list[Union[TypeVariable, TypeConstructorVariable]] - def __init__(self, type_class3: 'Type3Class', types: Iterable[TypeVariable]) -> None: + def __init__(self, type_class3: 'Type3Class', types: Iterable[Union[TypeVariable, TypeConstructorVariable]]) -> None: self.type_class3 = type_class3 self.types = list(types) @@ -52,27 +68,22 @@ class Constraint_TypeClassInstanceExists(ConstraintBase): assert len(self.type_class3.args) == len(self.types) class TypeVariableContext: - __slots__ = ('variables', 'constraints', ) + __slots__ = ('constraints', ) - variables: set[TypeVariable] constraints: list[ConstraintBase] - def __init__(self) -> None: - self.variables = set() - self.constraints = [] + def __init__(self, constraints: Iterable[ConstraintBase] = ()) -> None: + self.constraints = list(constraints) def __copy__(self) -> 'TypeVariableContext': - result = TypeVariableContext() - result.variables.update(self.variables) - result.constraints.extend(self.constraints) - return result + return TypeVariableContext(self.constraints) class FunctionSignature: __slots__ = ('context', 'args', ) context: TypeVariableContext - args: List[Union['Type3', TypeVariable]] + args: List[Union['Type3', TypeVariable, TypeConstructorVariable]] - def __init__(self, context: TypeVariableContext, args: Iterable[Union['Type3', TypeVariable]]) -> None: + def __init__(self, context: TypeVariableContext, args: Iterable[Union['Type3', TypeVariable, TypeConstructorVariable]]) -> None: self.context = context.__copy__() self.args = list(args) diff --git a/phasm/type3/typeclasses.py b/phasm/type3/typeclasses.py index dcb63b6..f86be6e 100644 --- a/phasm/type3/typeclasses.py +++ b/phasm/type3/typeclasses.py @@ -3,6 +3,7 @@ from typing import Dict, Iterable, List, Mapping, Optional, Union from .functions import ( Constraint_TypeClassInstanceExists, FunctionSignature, + TypeConstructorVariable, TypeVariable, TypeVariableContext, ) @@ -26,7 +27,7 @@ class Type3Class: __slots__ = ('name', 'args', 'methods', 'operators', 'inherited_classes', ) name: str - args: List[TypeVariable] + args: List[Union[TypeVariable, TypeConstructorVariable]] methods: Dict[str, Type3ClassMethod] operators: Dict[str, Type3ClassMethod] inherited_classes: List['Type3Class'] @@ -34,9 +35,9 @@ class Type3Class: def __init__( self, name: str, - args: Iterable[TypeVariable], - methods: Mapping[str, Iterable[Union[Type3, TypeVariable]]], - operators: Mapping[str, Iterable[Union[Type3, TypeVariable]]], + args: Iterable[Union[TypeVariable, TypeConstructorVariable]], + methods: Mapping[str, Union[FunctionSignature, Iterable[Union[Type3, TypeVariable, TypeConstructorVariable]]]], + operators: Mapping[str, Union[FunctionSignature, Iterable[Union[Type3, TypeVariable, TypeConstructorVariable]]]], inherited_classes: Optional[List['Type3Class']] = None, ) -> None: self.name = name @@ -46,11 +47,11 @@ class Type3Class: context.constraints.append(Constraint_TypeClassInstanceExists(self, args)) self.methods = { - k: Type3ClassMethod(k, FunctionSignature(context, v)) + k: Type3ClassMethod(k, v if isinstance(v, FunctionSignature) else FunctionSignature(context, v)) for k, v in methods.items() } self.operators = { - k: Type3ClassMethod(k, FunctionSignature(context, v)) + k: Type3ClassMethod(k, v if isinstance(v, FunctionSignature) else FunctionSignature(context, v)) for k, v in operators.items() } self.inherited_classes = inherited_classes or [] diff --git a/phasm/type3/types.py b/phasm/type3/types.py index 6dad13c..4052504 100644 --- a/phasm/type3/types.py +++ b/phasm/type3/types.py @@ -106,7 +106,7 @@ class TypeConstructor(Generic[T]): The name of the type constructor """ - on_create: Callable[[Type3], None] + on_create: Callable[[T, Type3], None] """ Who to let know if a type is created """ @@ -122,7 +122,7 @@ class TypeConstructor(Generic[T]): Sometimes we need to know the key that created a type. """ - def __init__(self, name: str, on_create: Callable[[Type3], None]) -> None: + def __init__(self, name: str, on_create: Callable[[T, Type3], None]) -> None: self.name = name self.on_create = on_create @@ -152,7 +152,7 @@ class TypeConstructor(Generic[T]): if result is None: self._cache[key] = result = Type3(self.make_name(key)) self._reverse_cache[result] = key - self.on_create(result) + self.on_create(key, result) return result diff --git a/tests/integration/test_lang/test_foldable.py b/tests/integration/test_lang/test_foldable.py new file mode 100644 index 0000000..12f5fe8 --- /dev/null +++ b/tests/integration/test_lang/test_foldable.py @@ -0,0 +1,41 @@ +import pytest + +from phasm.type3.entry import Type3Exception + +from ..helpers import Suite + + +@pytest.mark.integration_test +def test_foldable_sum(): + code_py = """ +@exported +def testEntry(x: i32[5]) -> i32: + return sum(x) +""" + + result = Suite(code_py).run_code((4, 5, 6, 7, 8, )) + + assert 30 == result.returned_value + + +@pytest.mark.integration_test +def test_foldable_invalid_return_type(): + code_py = """ +@exported +def testEntry(x: i32[5]) -> f64: + return sum(x) +""" + + with pytest.raises(Type3Exception, match='f64 must be i32 instead'): + Suite(code_py).run_code((4, 5, 6, 7, 8, )) + +@pytest.mark.integration_test +def test_foldable_not_foldable(): + code_py = """ +@exported +def testEntry(x: u8) -> u8: + return sum(x) +""" + + with pytest.raises(Type3Exception, match='Missing type class instantation: Foldable u8'): + Suite(code_py).run_code()