Implements sum for Foldable types

Foldable take a TypeConstructor. The first argument must be a
NatNum.
This commit is contained in:
Johan B.W. de Vries 2025-05-05 14:09:38 +02:00
parent f6cb1a8c1d
commit 8a027a0b10
9 changed files with 172 additions and 35 deletions

View File

@ -2,7 +2,7 @@
This module contains the code to convert parsed Ourlang into WebAssembly code This module contains the code to convert parsed Ourlang into WebAssembly code
""" """
import struct import struct
from typing import Dict, List, Optional from typing import Dict, List, Optional, Union
from . import codestyle, ourlang, prelude, wasm from . import codestyle, ourlang, prelude, wasm
from .runtime import calculate_alloc_size, calculate_member_offset from .runtime import calculate_alloc_size, calculate_member_offset
@ -259,6 +259,10 @@ INSTANCES = {
prelude.Promotable.methods['demote']: { prelude.Promotable.methods['demote']: {
'a=f32,b=f64': stdlib_types.f32_f64_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: 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 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): 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): if not isinstance(type_var, type3functions.TypeVariable):
@ -488,12 +492,16 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
type_var_map = {} type_var_map = {}
for type_var, arg_expr in zip(inp.function.signature.args, inp.arguments + [inp], strict=True): 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 # Fixed type, not part of the lookup requirements
continue continue
assert isinstance(arg_expr.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR if isinstance(type_var, (type3functions.TypeVariable, type3functions.TypeConstructorVariable, )):
type_var_map[type_var] = arg_expr.type3 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( instance_key = ','.join(
f'{k.letter}={v.name}' f'{k.letter}={v.name}'
@ -960,6 +968,7 @@ def module(inp: ourlang.Module) -> wasm.Module:
stdlib_types.__u32_pow2__, stdlib_types.__u32_pow2__,
stdlib_types.__u8_rotl__, stdlib_types.__u8_rotl__,
stdlib_types.__u8_rotr__, stdlib_types.__u8_rotr__,
stdlib_types.__sa_i32_sum__,
] + [ ] + [
function(x) function(x)
for x in inp.functions.values() for x in inp.functions.values()

View File

@ -1,20 +1,29 @@
""" """
The prelude are all the builtin types, type classes and methods 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.typeclasses import Type3Class
from ..type3.types import ( from ..type3.types import (
IntType3,
Type3, Type3,
TypeConstructor,
TypeConstructor_StaticArray, TypeConstructor_StaticArray,
TypeConstructor_Struct, TypeConstructor_Struct,
TypeConstructor_Tuple, 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 global PRELUDE_TYPE_CLASS_INSTANCES_EXISTING
# TODO: Check for required existing instantiations # 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. 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) instance_type_class(InternalPassAsPointer, typ)
static_array = TypeConstructor_StaticArray('static_array', on_create=sa_on_create) 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. 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) instance_type_class(InternalPassAsPointer, typ)
tuple_ = TypeConstructor_Tuple('tuple', on_create=tp_on_create) tuple_ = TypeConstructor_Tuple('tuple', on_create=tp_on_create)
@ -143,6 +152,7 @@ PRELUDE_TYPES: dict[str, Type3] = {
a = TypeVariable('a') a = TypeVariable('a')
b = TypeVariable('b') b = TypeVariable('b')
t = TypeConstructorVariable('t', [])
InternalPassAsPointer = Type3Class('InternalPassAsPointer', [a], methods={}, operators={}) InternalPassAsPointer = Type3Class('InternalPassAsPointer', [a], methods={}, operators={})
""" """
@ -286,6 +296,17 @@ Promotable = Type3Class('Promotable', [a, b], methods={
instance_type_class(Promotable, f32, f64) 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 = { PRELUDE_TYPE_CLASSES = {
'Eq': Eq, 'Eq': Eq,
'Ord': Ord, 'Ord': Ord,
@ -321,4 +342,5 @@ PRELUDE_METHODS = {
**Sized_.methods, **Sized_.methods,
**Extendable.methods, **Extendable.methods,
**Promotable.methods, **Promotable.methods,
**Foldable.methods,
} }

View File

@ -384,6 +384,50 @@ def __u8_rotr__(g: Generator, x: i32, r: i32) -> i32:
return i32('return') # To satisfy mypy 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 ## class Eq
@ -920,3 +964,11 @@ def f32_f64_promote(g: Generator) -> None:
def f32_f64_demote(g: Generator) -> None: def f32_f64_demote(g: Generator) -> None:
g.f32.demote_f64() 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__')

View File

@ -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. 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 ourlang, prelude
from . import placeholders, typeclasses, types from . import placeholders, typeclasses, types
@ -48,7 +48,7 @@ class Context:
__slots__ = ('type_class_instances_existing', ) __slots__ = ('type_class_instances_existing', )
# Constraint_TypeClassInstanceExists # 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: def __init__(self) -> None:
self.type_class_instances_existing = set() self.type_class_instances_existing = set()

View File

@ -58,6 +58,7 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator:
x: placeholders.PlaceholderForType([]) x: placeholders.PlaceholderForType([])
for x in signature.args for x in signature.args
if isinstance(x, functions.TypeVariable) if isinstance(x, functions.TypeVariable)
or isinstance(x, functions.TypeConstructorVariable)
} }
for call_arg in arguments: for call_arg in arguments:
@ -80,7 +81,7 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator:
else: else:
comment = f'The type of the value passed to argument {arg_no} of function {func_name} should match the type of that argument' 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) yield SameTypeConstraint(type_var_map[sig_part], arg_expr.type3, comment=comment)
continue continue

View File

@ -34,6 +34,22 @@ class TypeVariable:
def __repr__(self) -> str: def __repr__(self) -> str:
return f'TypeVariable({repr(self.letter)})' 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: class ConstraintBase:
__slots__ = () __slots__ = ()
@ -41,9 +57,9 @@ class Constraint_TypeClassInstanceExists(ConstraintBase):
__slots__ = ('type_class3', 'types', ) __slots__ = ('type_class3', 'types', )
type_class3: 'Type3Class' 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.type_class3 = type_class3
self.types = list(types) self.types = list(types)
@ -52,27 +68,22 @@ class Constraint_TypeClassInstanceExists(ConstraintBase):
assert len(self.type_class3.args) == len(self.types) assert len(self.type_class3.args) == len(self.types)
class TypeVariableContext: class TypeVariableContext:
__slots__ = ('variables', 'constraints', ) __slots__ = ('constraints', )
variables: set[TypeVariable]
constraints: list[ConstraintBase] constraints: list[ConstraintBase]
def __init__(self) -> None: def __init__(self, constraints: Iterable[ConstraintBase] = ()) -> None:
self.variables = set() self.constraints = list(constraints)
self.constraints = []
def __copy__(self) -> 'TypeVariableContext': def __copy__(self) -> 'TypeVariableContext':
result = TypeVariableContext() return TypeVariableContext(self.constraints)
result.variables.update(self.variables)
result.constraints.extend(self.constraints)
return result
class FunctionSignature: class FunctionSignature:
__slots__ = ('context', 'args', ) __slots__ = ('context', 'args', )
context: TypeVariableContext 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.context = context.__copy__()
self.args = list(args) self.args = list(args)

View File

@ -3,6 +3,7 @@ from typing import Dict, Iterable, List, Mapping, Optional, Union
from .functions import ( from .functions import (
Constraint_TypeClassInstanceExists, Constraint_TypeClassInstanceExists,
FunctionSignature, FunctionSignature,
TypeConstructorVariable,
TypeVariable, TypeVariable,
TypeVariableContext, TypeVariableContext,
) )
@ -26,7 +27,7 @@ class Type3Class:
__slots__ = ('name', 'args', 'methods', 'operators', 'inherited_classes', ) __slots__ = ('name', 'args', 'methods', 'operators', 'inherited_classes', )
name: str name: str
args: List[TypeVariable] args: List[Union[TypeVariable, TypeConstructorVariable]]
methods: Dict[str, Type3ClassMethod] methods: Dict[str, Type3ClassMethod]
operators: Dict[str, Type3ClassMethod] operators: Dict[str, Type3ClassMethod]
inherited_classes: List['Type3Class'] inherited_classes: List['Type3Class']
@ -34,9 +35,9 @@ class Type3Class:
def __init__( def __init__(
self, self,
name: str, name: str,
args: Iterable[TypeVariable], args: Iterable[Union[TypeVariable, TypeConstructorVariable]],
methods: Mapping[str, Iterable[Union[Type3, TypeVariable]]], methods: Mapping[str, Union[FunctionSignature, Iterable[Union[Type3, TypeVariable, TypeConstructorVariable]]]],
operators: Mapping[str, Iterable[Union[Type3, TypeVariable]]], operators: Mapping[str, Union[FunctionSignature, Iterable[Union[Type3, TypeVariable, TypeConstructorVariable]]]],
inherited_classes: Optional[List['Type3Class']] = None, inherited_classes: Optional[List['Type3Class']] = None,
) -> None: ) -> None:
self.name = name self.name = name
@ -46,11 +47,11 @@ class Type3Class:
context.constraints.append(Constraint_TypeClassInstanceExists(self, args)) context.constraints.append(Constraint_TypeClassInstanceExists(self, args))
self.methods = { 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() for k, v in methods.items()
} }
self.operators = { 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() for k, v in operators.items()
} }
self.inherited_classes = inherited_classes or [] self.inherited_classes = inherited_classes or []

View File

@ -106,7 +106,7 @@ class TypeConstructor(Generic[T]):
The name of the type constructor 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 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. 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.name = name
self.on_create = on_create self.on_create = on_create
@ -152,7 +152,7 @@ class TypeConstructor(Generic[T]):
if result is None: if result is None:
self._cache[key] = result = Type3(self.make_name(key)) self._cache[key] = result = Type3(self.make_name(key))
self._reverse_cache[result] = key self._reverse_cache[result] = key
self.on_create(result) self.on_create(key, result)
return result return result

View File

@ -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()