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

View File

@ -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,
}

View File

@ -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__')

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.
"""
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()

View File

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

View File

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

View File

@ -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 []

View File

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

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