Ideas [skip-ci]

This commit is contained in:
Johan B.W. de Vries 2025-04-11 15:50:52 +02:00
parent 87866cff55
commit a59bc9c31d
13 changed files with 382 additions and 336 deletions

View File

@ -15,9 +15,7 @@
- static_array and tuple should probably not be PrimitiveType3, but instead subclass AppliedType3?
- See if we want to replace Fractional with Real, and add Rational, Irrationl, Algebraic, Transendental
- test_bitwise_or_inv_type
- test_bytes_index_out_of_bounds vs static trap(?)
- test_num.py is probably best as part of the generator?
- Find pytest.mark.skip
- There's a weird resolve_as reference in calculate_alloc_size
- Either there should be more of them or less
@ -36,3 +34,4 @@
- Filter out methods that aren't used; other the other way around (easier?) only add __ methods when needed
- Move UnaryOp.operator into type class methods
- Move foldr into type class methods
- PrimitiveType is just the type, nothing primitive about it (change the name). u32 are still basic types or something.

View File

@ -27,21 +27,6 @@ def type3(inp: Type3OrPlaceholder) -> str:
if inp is type3types.none:
return 'None'
if isinstance(inp, type3types.AppliedType3):
if inp.base == type3types.tuple:
return '(' + ', '.join(
type3(x)
for x in inp.args
if isinstance(x, Type3) # Skip ints, not allowed here anyhow
) + ', )'
if inp.base == type3types.static_array:
assert 2 == len(inp.args)
assert isinstance(inp.args[0], Type3), TYPE3_ASSERTION_ERROR
assert isinstance(inp.args[1], type3types.IntType3), TYPE3_ASSERTION_ERROR
return type3(inp.args[0]) + '[' + inp.args[1].name + ']'
return inp.name
def struct_definition(inp: ourlang.StructDefinition) -> str:

View File

@ -286,7 +286,7 @@ def type3(inp: type3types.Type3OrPlaceholder) -> wasm.WasmType:
if inp == type3types.f64:
return wasm.WasmTypeFloat64()
if inp == type3types.bytes:
if inp == type3types.bytes_:
# bytes are passed as pointer
# And pointers are i32
return wasm.WasmTypeInt32()
@ -295,13 +295,7 @@ def type3(inp: type3types.Type3OrPlaceholder) -> wasm.WasmType:
# Structs are passed as pointer, which are i32
return wasm.WasmTypeInt32()
if isinstance(inp, type3types.AppliedType3):
if inp.base == type3types.static_array:
# Static Arrays are passed as pointer, which are i32
return wasm.WasmTypeInt32()
if inp.base == type3types.tuple:
# Tuples are passed as pointer, which are i32
if type3classes.InternalPassAsPointer in inp.classes:
return wasm.WasmTypeInt32()
raise NotImplementedError(type3, inp)
@ -310,9 +304,21 @@ def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) ->
"""
Compile: Instantiation (allocation) of a tuple
"""
assert isinstance(inp.type3, type3types.AppliedType3)
assert inp.type3.base is type3types.tuple
assert len(inp.elements) == len(inp.type3.args)
assert isinstance(inp.type3, type3types.PrimitiveType3)
args: list[type3types.PrimitiveType3] = []
sa_args = type3types.static_array.did_construct(inp.type3)
if sa_args is not None:
sa_type, sa_len = sa_args
args = [sa_type for _ in range(sa_len.value)]
if not args:
tp_args = type3types.tuple_.did_construct(inp.type3)
if tp_args is None:
raise NotImplementedError
args = list(tp_args)
comment_elements = ''
for element in inp.elements:
@ -329,14 +335,15 @@ def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) ->
# Store each element individually
offset = 0
for element, exp_type3 in zip(inp.elements, inp.type3.args):
for element, exp_type3 in zip(inp.elements, args):
if isinstance(exp_type3, type3types.PlaceholderForType):
assert exp_type3.resolve_as is not None
assert isinstance(exp_type3.resolve_as, type3types.PrimitiveType3)
exp_type3 = exp_type3.resolve_as
assert element.type3 == exp_type3
if isinstance(exp_type3, type3types.StructType3) or isinstance(exp_type3, type3types.AppliedType3):
if type3classes.InternalPassAsPointer in exp_type3.classes:
mtyp = 'i32'
else:
assert isinstance(exp_type3, type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs')
@ -357,6 +364,9 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
"""
Compile: Any expression
"""
if isinstance(inp, ourlang.ConstantTuple):
raise Exception
if isinstance(inp, ourlang.ConstantPrimitive):
assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
@ -401,11 +411,20 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
if isinstance(inp.variable, ourlang.ModuleConstantDef):
assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
if type3classes.InternalPassAsPointer in inp.type3.classes:
# FIXME: Artifact from older version
assert isinstance(inp.variable.constant, ourlang.ConstantTuple)
address = inp.variable.constant.data_block.address
assert address is not None, 'Value not allocated'
wgn.i32.const(address)
return
if isinstance(inp.type3, type3types.PrimitiveType3):
expression(wgn, inp.variable.constant)
return
if inp.type3 is type3types.bytes:
if inp.type3 is type3types.bytes_:
assert isinstance(inp.variable.constant, ourlang.ConstantBytes)
address = inp.variable.constant.data_block.address
@ -421,25 +440,6 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
wgn.i32.const(address)
return
if isinstance(inp.type3, type3types.AppliedType3):
if inp.type3.base == type3types.static_array:
assert isinstance(inp.variable.constant, ourlang.ConstantTuple)
address = inp.variable.constant.data_block.address
assert address is not None, 'Value not allocated'
wgn.i32.const(address)
return
if inp.type3.base == type3types.tuple:
assert isinstance(inp.variable.constant, ourlang.ConstantTuple)
address = inp.variable.constant.data_block.address
assert address is not None, 'Value not allocated'
wgn.i32.const(address)
return
raise NotImplementedError(expression, inp.variable, inp.type3.base)
raise NotImplementedError(expression, inp)
raise NotImplementedError(expression, inp.variable)
@ -479,7 +479,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
if inp.type3 == type3types.u32:
if inp.operator == 'len':
if inp.right.type3 == type3types.bytes:
if inp.right.type3 == type3types.bytes_:
wgn.i32.load()
return
@ -528,71 +528,71 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
if isinstance(inp, ourlang.Subscript):
assert isinstance(inp.varref.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
if inp.varref.type3 is type3types.bytes:
if inp.varref.type3 is type3types.bytes_:
expression(wgn, inp.varref)
expression(wgn, inp.index)
wgn.call(stdlib_types.__subscript_bytes__)
return
if isinstance(inp.varref.type3, type3types.AppliedType3):
if inp.varref.type3.base == type3types.static_array:
assert 2 == len(inp.varref.type3.args)
el_type = inp.varref.type3.args[0]
assert isinstance(el_type, type3types.Type3)
el_len = inp.varref.type3.args[1]
assert isinstance(el_len, type3types.IntType3)
# if isinstance(inp.varref.type3, type3types.AppliedType3):
# if inp.varref.type3.base == type3types.static_array:
# assert 2 == len(inp.varref.type3.args)
# el_type = inp.varref.type3.args[0]
# assert isinstance(el_type, type3types.Type3)
# el_len = inp.varref.type3.args[1]
# assert isinstance(el_len, type3types.IntType3)
# OPTIMIZE: If index is a constant, we can use offset instead of multiply
# and we don't need to do the out of bounds check
# # OPTIMIZE: If index is a constant, we can use offset instead of multiply
# # and we don't need to do the out of bounds check
expression(wgn, inp.varref)
# expression(wgn, inp.varref)
tmp_var = wgn.temp_var_i32('index')
expression(wgn, inp.index)
wgn.local.tee(tmp_var)
# tmp_var = wgn.temp_var_i32('index')
# expression(wgn, inp.index)
# wgn.local.tee(tmp_var)
# Out of bounds check based on el_len.value
wgn.i32.const(el_len.value)
wgn.i32.ge_u()
with wgn.if_():
wgn.unreachable(comment='Out of bounds')
# # Out of bounds check based on el_len.value
# wgn.i32.const(el_len.value)
# wgn.i32.ge_u()
# with wgn.if_():
# wgn.unreachable(comment='Out of bounds')
wgn.local.get(tmp_var)
wgn.i32.const(calculate_alloc_size(el_type))
wgn.i32.mul()
wgn.i32.add()
# wgn.local.get(tmp_var)
# wgn.i32.const(calculate_alloc_size(el_type))
# wgn.i32.mul()
# wgn.i32.add()
assert isinstance(el_type, type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs')
mtyp = LOAD_STORE_TYPE_MAP[el_type.name]
# assert isinstance(el_type, type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs')
# mtyp = LOAD_STORE_TYPE_MAP[el_type.name]
wgn.add_statement(f'{mtyp}.load')
return
# wgn.add_statement(f'{mtyp}.load')
# return
if inp.varref.type3.base == type3types.tuple:
assert isinstance(inp.index, ourlang.ConstantPrimitive)
assert isinstance(inp.index.value, int)
# if inp.varref.type3.base == type3types.tuple:
# assert isinstance(inp.index, ourlang.ConstantPrimitive)
# assert isinstance(inp.index.value, int)
offset = 0
for el_type in inp.varref.type3.args[0:inp.index.value]:
assert isinstance(el_type, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
offset += calculate_alloc_size(el_type)
# offset = 0
# for el_type in inp.varref.type3.args[0:inp.index.value]:
# assert isinstance(el_type, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
# offset += calculate_alloc_size(el_type)
# This doubles as the out of bounds check
el_type = inp.varref.type3.args[inp.index.value]
assert isinstance(el_type, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
# # This doubles as the out of bounds check
# el_type = inp.varref.type3.args[inp.index.value]
# assert isinstance(el_type, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
expression(wgn, inp.varref)
# expression(wgn, inp.varref)
if isinstance(el_type, type3types.StructType3):
mtyp = 'i32'
elif isinstance(el_type, type3types.AppliedType3) and el_type.base is type3types.tuple:
mtyp = 'i32'
else:
assert isinstance(el_type, type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs')
mtyp = LOAD_STORE_TYPE_MAP[el_type.name]
# if isinstance(el_type, type3types.StructType3):
# mtyp = 'i32'
# elif type3classes.InternalPassAsPointer in el_type.classes:
# mtyp = 'i32'
# else:
# assert isinstance(el_type, type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs')
# mtyp = LOAD_STORE_TYPE_MAP[el_type.name]
wgn.add_statement(f'{mtyp}.load', f'offset={offset}')
return
# wgn.add_statement(f'{mtyp}.load', f'offset={offset}')
# return
raise NotImplementedError(expression, inp, inp.varref.type3)
@ -618,7 +618,7 @@ def expression_fold(wgn: WasmGenerator, inp: ourlang.Fold) -> None:
"""
assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
if inp.iter.type3 is not type3types.bytes:
if inp.iter.type3 is not type3types.bytes_:
raise NotImplementedError(expression_fold, inp, inp.iter.type3)
wgn.add_statement('nop', comment='acu :: u8')
@ -694,6 +694,7 @@ def statement_return(wgn: WasmGenerator, inp: ourlang.StatementReturn) -> None:
"""
Compile: Return statement
"""
print('inp', inp)
expression(wgn, inp.value)
wgn.return_()
@ -906,7 +907,7 @@ def module_data(inp: ourlang.ModuleData) -> bytes:
data_list.append(module_data_f64(constant.value))
continue
if constant.type3 == type3types.bytes:
if constant.type3 == type3types.bytes_:
assert isinstance(constant, ourlang.ConstantBytes)
assert isinstance(constant.value, bytes)
data_list.append(module_data_u32(len(constant.value)))
@ -987,7 +988,7 @@ def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstruc
# Store each member individually
for memname, mtyp3 in inp.struct_type3.members.items():
mtyp: Optional[str]
if isinstance(mtyp3, type3types.StructType3) or isinstance(mtyp3, type3types.AppliedType3):
if type3classes.InternalPassAsPointer in mtyp3.classes:
mtyp = 'i32'
else:
mtyp = LOAD_STORE_TYPE_MAP.get(mtyp3.name)

View File

@ -275,6 +275,9 @@ class StatementReturn(Statement):
def __init__(self, value: Expression) -> None:
self.value = value
def __repr__(self) -> str:
return f'StatementReturn({repr(self.value)})'
class StatementIf(Statement):
"""
An if statement within a function

View File

@ -664,7 +664,7 @@ class OurVisitor:
raise NotImplementedError(f'{node.value} as constant')
def visit_type(self, module: Module, node: ast.expr) -> type3types.Type3:
def visit_type(self, module: Module, node: ast.expr) -> type3types.PrimitiveType3:
if isinstance(node, ast.Constant):
if node.value is None:
return type3types.none
@ -693,18 +693,17 @@ class OurVisitor:
if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
return type3types.AppliedType3(
type3types.static_array,
[self.visit_type(module, node.value), type3types.IntType3(node.slice.value)],
return type3types.static_array(
self.visit_type(module, node.value),
type3types.IntType3(node.slice.value),
)
if isinstance(node, ast.Tuple):
if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
return type3types.AppliedType3(
type3types.tuple,
(self.visit_type(module, elt) for elt in node.elts)
return type3types.tuple_(
*(self.visit_type(module, elt) for elt in node.elts)
)
raise NotImplementedError(f'{node} as type')

View File

@ -11,7 +11,7 @@ def calculate_alloc_size(typ: type3types.Type3, is_member: bool = False) -> int:
if typ in (type3types.u64, type3types.i64, type3types.f64, ):
return 8
if typ == type3types.bytes:
if typ == type3types.bytes_:
if is_member:
return 4
@ -27,28 +27,30 @@ def calculate_alloc_size(typ: type3types.Type3, is_member: bool = False) -> int:
for x in typ.members.values()
)
if isinstance(typ, type3types.AppliedType3):
if typ.base is type3types.static_array:
assert isinstance(typ, type3types.PrimitiveType3)
sa_args = type3types.static_array.did_construct(typ)
if sa_args is not None:
if is_member:
# tuples referred to by other structs or tuples are pointers
return 4
assert isinstance(typ.args[0], type3types.Type3)
assert isinstance(typ.args[1], type3types.IntType3)
sa_type, sa_len = sa_args
return typ.args[1].value * calculate_alloc_size(typ.args[0], is_member=True)
return sa_len.value * calculate_alloc_size(sa_type, is_member=True)
if typ.base is type3types.tuple:
tp_args = type3types.tuple_.did_construct(typ)
if tp_args is not None:
if is_member:
# tuples referred to by other structs or tuples are pointers
return 4
size = 0
for arg in typ.args:
for arg in tp_args:
assert not isinstance(arg, type3types.IntType3)
if isinstance(arg, type3types.PlaceholderForType):
assert arg.resolve_as is not None
assert isinstance(arg.resolve_as, type3types.PrimitiveType3)
arg = arg.resolve_as
size += calculate_alloc_size(arg, is_member=True)

View File

@ -112,10 +112,6 @@ class SameTypeConstraint(ConstraintBase):
known_types.append(typ)
continue
if isinstance(typ, types.AppliedType3):
known_types.append(typ)
continue
if isinstance(typ, types.PlaceholderForType):
if typ.resolve_as is not None:
known_types.append(typ.resolve_as)
@ -132,38 +128,6 @@ class SameTypeConstraint(ConstraintBase):
first_type = known_types[0]
for typ in known_types[1:]:
if isinstance(first_type, types.AppliedType3) and isinstance(typ, types.AppliedType3):
if first_type.base is types.tuple and typ.base is types.static_array:
# Swap so we can reuse the code below
# Hope that it still gives proper type errors
first_type, typ = typ, first_type
if first_type.base is types.static_array and typ.base is types.tuple:
assert isinstance(first_type.args[1], types.IntType3)
length = first_type.args[1].value
if len(typ.args) != length:
return Error('Mismatch between applied types argument count', comment=self.comment)
for typ_arg in typ.args:
new_constraint_list.append(SameTypeConstraint(
first_type.args[0], typ_arg
))
continue
if first_type.base != typ.base:
return Error('Mismatch between applied types base', comment=self.comment)
if len(first_type.args) != len(typ.args):
return Error('Mismatch between applied types argument count', comment=self.comment)
for first_type_arg, typ_arg in zip(first_type.args, typ.args):
new_constraint_list.append(SameTypeConstraint(
first_type_arg, typ_arg
))
continue
if typ != first_type:
return Error(f'{typ:s} must be {first_type:s} instead', comment=self.comment)
@ -198,6 +162,47 @@ class SameTypeConstraint(ConstraintBase):
return f'SameTypeConstraint({args}, comment={repr(self.comment)})'
class TupleMatchConstraint(ConstraintBase):
def __init__(self, exp_type: types.Type3OrPlaceholder, args: List[types.Type3OrPlaceholder], comment: str):
super().__init__(comment=comment)
self.exp_type = exp_type
self.args = list(args)
def check(self) -> CheckResult:
exp_type = self.exp_type
if isinstance(exp_type, types.PlaceholderForType):
if exp_type.resolve_as is None:
return RequireTypeSubstitutes()
exp_type = exp_type.resolve_as
assert isinstance(exp_type, types.PrimitiveType3)
sa_args = types.static_array.did_construct(exp_type)
if sa_args is not None:
sa_type, sa_len = sa_args
if sa_len.value != len(self.args):
return Error('Mismatch between applied types argument count', comment=self.comment)
return [
SameTypeConstraint(arg, sa_type)
for arg in self.args
]
tp_args = types.tuple_.did_construct(exp_type)
if tp_args is not None:
if len(tp_args) != len(self.args):
return Error('Mismatch between applied types argument count', comment=self.comment)
return [
SameTypeConstraint(arg, oth_arg)
for arg, oth_arg in zip(self.args, tp_args)
]
raise NotImplementedError(exp_type)
class IntegerCompareConstraint(ConstraintBase):
"""
Verifies that the given IntType3 are in order (<=)
@ -401,7 +406,7 @@ class LiteralFitsConstraint(ConstraintBase):
return Error('Must be real', comment=self.comment) # FIXME: Add line information
if self.type3 is types.bytes:
if self.type3 is types.bytes_:
if isinstance(self.literal.value, bytes):
return None
@ -409,45 +414,47 @@ class LiteralFitsConstraint(ConstraintBase):
res: NewConstraintList
if isinstance(self.type3, types.AppliedType3):
if self.type3.base == types.tuple:
assert isinstance(self.type3, types.PrimitiveType3)
tp_args = types.tuple_.did_construct(self.type3)
if tp_args is not None:
if not isinstance(self.literal, ourlang.ConstantTuple):
return Error('Must be tuple', comment=self.comment)
if len(self.type3.args) != len(self.literal.value):
if len(tp_args) != len(self.literal.value):
return Error('Tuple element count mismatch', comment=self.comment)
res = []
res.extend(
LiteralFitsConstraint(x, y)
for x, y in zip(self.type3.args, self.literal.value)
for x, y in zip(tp_args, self.literal.value)
)
res.extend(
SameTypeConstraint(x, y.type3)
for x, y in zip(self.type3.args, self.literal.value)
for x, y in zip(tp_args, self.literal.value)
)
return res
if self.type3.base == types.static_array:
sa_args = types.static_array.did_construct(self.type3)
if sa_args is not None:
if not isinstance(self.literal, ourlang.ConstantTuple):
return Error('Must be tuple', comment=self.comment)
assert 2 == len(self.type3.args)
assert isinstance(self.type3.args[1], types.IntType3)
sa_type, sa_len = sa_args
if self.type3.args[1].value != len(self.literal.value):
if sa_len.value != len(self.literal.value):
return Error('Member count mismatch', comment=self.comment)
res = []
res.extend(
LiteralFitsConstraint(self.type3.args[0], y)
LiteralFitsConstraint(sa_type, y)
for y in self.literal.value
)
res.extend(
SameTypeConstraint(self.type3.args[0], y.type3)
SameTypeConstraint(sa_type, y.type3)
for y in self.literal.value
)
@ -517,42 +524,42 @@ class CanBeSubscriptedConstraint(ConstraintBase):
self.type3 = self.type3.resolve_as
if isinstance(self.type3, types.AppliedType3):
if self.type3.base == types.static_array:
result: List[ConstraintBase] = [
SameTypeConstraint(types.u32, self.index_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'),
SameTypeConstraint(self.type3.args[0], self.ret_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'),
]
# if isinstance(self.type3, types.AppliedType3):
# if self.type3.base == types.static_array:
# result: List[ConstraintBase] = [
# SameTypeConstraint(types.u32, self.index_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'),
# SameTypeConstraint(self.type3.args[0], self.ret_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'),
# ]
if isinstance(self.index, ourlang.ConstantPrimitive):
assert isinstance(self.index.value, int)
assert isinstance(self.type3.args[1], types.IntType3)
# if isinstance(self.index, ourlang.ConstantPrimitive):
# assert isinstance(self.index.value, int)
# assert isinstance(self.type3.args[1], types.IntType3)
result.append(
IntegerCompareConstraint(
types.IntType3(0), types.IntType3(self.index.value), types.IntType3(self.type3.args[1].value - 1),
comment='Subscript static array must fit the size of the array'
)
)
# result.append(
# IntegerCompareConstraint(
# types.IntType3(0), types.IntType3(self.index.value), types.IntType3(self.type3.args[1].value - 1),
# comment='Subscript static array must fit the size of the array'
# )
# )
return result
# return result
if self.type3.base == types.tuple:
if not isinstance(self.index, ourlang.ConstantPrimitive):
return Error('Must index with literal')
# if self.type3.base == types.tuple:
# if not isinstance(self.index, ourlang.ConstantPrimitive):
# return Error('Must index with literal')
if not isinstance(self.index.value, int):
return Error('Must index with integer literal')
# if not isinstance(self.index.value, int):
# return Error('Must index with integer literal')
if self.index.value < 0 or len(self.type3.args) <= self.index.value:
return Error('Tuple index out of range')
# if self.index.value < 0 or len(self.type3.args) <= self.index.value:
# return Error('Tuple index out of range')
return [
SameTypeConstraint(types.u32, self.index_type3, comment=f'Tuple subscript index {self.index.value}'),
SameTypeConstraint(self.type3.args[self.index.value], self.ret_type3, comment=f'Tuple subscript index {self.index.value}'),
]
# return [
# SameTypeConstraint(types.u32, self.index_type3, comment=f'Tuple subscript index {self.index.value}'),
# SameTypeConstraint(self.type3.args[self.index.value], self.ret_type3, comment=f'Tuple subscript index {self.index.value}'),
# ]
if self.type3 is types.bytes:
if self.type3 is types.bytes_:
return [
SameTypeConstraint(types.u32, self.index_type3, comment='([]) :: bytes -> u32 -> u8'),
SameTypeConstraint(types.u8, self.ret_type3, comment='([]) :: bytes -> u32 -> u8'),

View File

@ -16,6 +16,7 @@ from .constraints import (
LiteralFitsConstraint,
MustImplementTypeClassConstraint,
SameTypeConstraint,
TupleMatchConstraint,
)
ConstraintGenerator = Generator[ConstraintBase, None, None]
@ -142,9 +143,9 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator:
yield from expression(ctx, arg)
r_type.append(arg.type3)
yield SameTypeConstraint(
yield TupleMatchConstraint(
inp.type3,
type3types.AppliedType3(type3types.tuple, r_type),
r_type,
comment='The type of a tuple is a combination of its members'
)

View File

@ -13,7 +13,6 @@ from .constraints import (
)
from .constraintsgenerator import phasm_type3_generate_constraints
from .types import (
AppliedType3,
IntType3,
PlaceholderForType,
PrimitiveType3,
@ -153,14 +152,6 @@ def get_printable_type_name(inp: Type3OrPlaceholder, placeholder_id_map: Dict[in
placeholder_id_map[placeholder_id] = 'T' + str(len(placeholder_id_map) + 1)
return placeholder_id_map[placeholder_id]
if isinstance(inp, AppliedType3):
return (
get_printable_type_name(inp.base, placeholder_id_map)
+ ' ('
+ ') ('.join(get_printable_type_name(x, placeholder_id_map) for x in inp.args)
+ ')'
)
raise NotImplementedError(inp)
def print_constraint_list(placeholder_id_map: Dict[int, str], constraint_list: List[ConstraintBase], placeholder_substitutes: SubstitutionMap) -> None:

View File

@ -93,6 +93,8 @@ class Type3Class:
def __repr__(self) -> str:
return self.name
InternalPassAsPointer = Type3Class('InternalPassAsPointer', ['a'], methods={}, operators={})
Eq = Type3Class('Eq', ['a'], methods={}, operators={
'==': 'a -> a -> bool',
'!=': 'a -> a -> bool',

View File

@ -4,7 +4,19 @@ Contains the final types for use in Phasm
These are actual, instantiated types; not the abstract types that the
constraint generator works with.
"""
from typing import Any, Dict, Iterable, List, Optional, Protocol, Set, Union
from typing import (
Any,
Dict,
Generic,
Iterable,
List,
Optional,
Protocol,
Set,
Tuple,
TypeVar,
Union,
)
from .typeclasses import (
Bits,
@ -12,6 +24,7 @@ from .typeclasses import (
Floating,
Fractional,
Integral,
InternalPassAsPointer,
IntNum,
NatNum,
Ord,
@ -83,7 +96,7 @@ class Type3:
return not self.__eq__(other)
def __hash__(self) -> int:
raise NotImplementedError
return hash(self.name)
def __bool__(self) -> bool:
raise NotImplementedError
@ -98,6 +111,9 @@ class PrimitiveType3(Type3):
class IntType3(Type3):
"""
Sometimes you can have an int as type, e.g. when using static arrays
This is not the same as an int on the language level.
[1.0, 1.2] :: Float[2] :: * -> Int -> *
"""
__slots__ = ('value', )
@ -119,6 +135,9 @@ class IntType3(Type3):
raise NotImplementedError
def __hash__(self) -> int:
return hash(self.value)
class PlaceholderForType:
"""
A placeholder type, for when we don't know the final type yet
@ -166,64 +185,100 @@ class PlaceholderForType:
Type3OrPlaceholder = Union[Type3, PlaceholderForType]
class AppliedType3(Type3):
"""
A Type3 that has been applied to another type
"""
__slots__ = ('base', 'args', )
T = TypeVar('T')
base: PrimitiveType3
class TypeConstructor(Generic[T]):
"""
The base type
Base class for type construtors
"""
__slots__ = ('name', 'classes', 'type_classes', '_cache', '_reverse_cache')
name: str
"""
The name of the type constructor
"""
args: List[Type3OrPlaceholder]
classes: Set[Type3Class]
"""
The applied types (or placeholders there for)
The type classes that this constructor implements
"""
def __init__(self, base: PrimitiveType3, args: Iterable[Type3OrPlaceholder]) -> None:
args = [*args]
assert args, 'Must at least one argument'
type_classes: Set[Type3Class]
"""
The type classes that the constructed types implement
"""
super().__init__(
base.name
+ ' ('
+ ') ('.join(str(x) for x in args) # FIXME: Do we need to redo the name on substitution?
+ ')',
[]
)
_cache: dict[T, PrimitiveType3]
"""
When constructing a type with the same arguments,
it should produce the exact same result.
"""
self.base = base
self.args = args
_reverse_cache: dict[PrimitiveType3, T]
"""
Sometimes we need to know the key that created a type.
"""
@property
def has_placeholders(self) -> bool:
return any(
isinstance(x, PlaceholderForType)
for x in self.args
)
def __init__(self, name: str, classes: Iterable[Type3Class], type_classes: Iterable[Type3Class]) -> None:
self.name = name
self.classes = set(classes)
self.type_classes = set(type_classes)
def __eq__(self, other: Any) -> bool:
if not isinstance(other, Type3):
self._cache = {}
self._reverse_cache = {}
def make_name(self, key: T) -> str:
raise NotImplementedError
if not isinstance(other, AppliedType3):
return False
def did_construct(self, typ: PrimitiveType3) -> T | None:
return self._reverse_cache.get(typ)
return (
self.base == other.base
and len(self.args) == len(other.args)
and all(
s == x
for s, x in zip(self.args, other.args)
)
)
def construct(self, key: T) -> PrimitiveType3:
result = self._cache.get(key, None)
if result is None:
self._cache[key] = result = PrimitiveType3(self.make_name(key), self.type_classes)
self._reverse_cache[result] = key
def __repr__(self) -> str:
return f'AppliedType3({repr(self.base)}, {repr(self.args)})'
return result
class StructType3(Type3):
class TypeConstructor_Type(TypeConstructor[PrimitiveType3]):
"""
Base class type constructors of kind: * -> *
"""
__slots__ = ()
def __call__(self, arg: PrimitiveType3) -> PrimitiveType3:
raise NotImplementedError
class TypeConstructor_TypeInt(TypeConstructor[Tuple[PrimitiveType3, IntType3]]):
"""
Base class type constructors of kind: * -> Int -> *
"""
__slots__ = ()
def make_name(self, key: Tuple[PrimitiveType3, IntType3]) -> str:
return f'{self.name} {key[0].name} {key[1].value}'
def __call__(self, arg0: PrimitiveType3, arg1: IntType3) -> PrimitiveType3:
return self.construct((arg0, arg1))
class TypeConstructor_TypeStar(TypeConstructor[Tuple[PrimitiveType3, ...]]):
"""
Base class type constructors of variadic kind
"""
def __call__(self, *args: PrimitiveType3) -> PrimitiveType3:
key: Tuple[PrimitiveType3, ...] = tuple(args)
return self.construct(key)
class TypeConstructor_StaticArray(TypeConstructor_TypeInt):
def make_name(self, key: Tuple[PrimitiveType3, IntType3]) -> str:
return f'{key[0].name}[{key[1].value}]'
class TypeConstructor_Tuple(TypeConstructor_TypeStar):
def make_name(self, key: Tuple[PrimitiveType3, ...]) -> str:
return '(' + ', '.join(x.name for x in key) + ', )'
class StructType3(PrimitiveType3):
"""
A Type3 struct with named members
"""
@ -315,20 +370,27 @@ f64 = PrimitiveType3('f64', [Eq, Floating, Fractional, IntNum, NatNum, Ord])
A 32-bits IEEE 754 float, of 64 bits width.
"""
bytes = PrimitiveType3('bytes', [])
bytes_ = PrimitiveType3('bytes', [])
"""
This is a runtime-determined length piece of memory that can be indexed at runtime.
"""
static_array = PrimitiveType3('static_array', [])
static_array = TypeConstructor_StaticArray('static_array', [], [
Eq,
InternalPassAsPointer,
])
"""
This is a fixed length piece of memory that can be indexed at runtime.
A type constructor.
Any static array is a fixed length piece of memory that can be indexed at runtime.
It should be applied with one argument. It has a runtime-dynamic length
of the same type repeated.
"""
tuple = PrimitiveType3('tuple', []) # pylint: disable=W0622
tuple_ = TypeConstructor_Tuple('tuple', [], [
InternalPassAsPointer,
])
"""
This is a fixed length piece of memory.
@ -336,7 +398,7 @@ It should be applied with zero or more arguments. It has a compile time
determined length, and each argument can be different.
"""
LOOKUP_TABLE: Dict[str, Type3] = {
LOOKUP_TABLE: Dict[str, PrimitiveType3] = {
'none': none,
'bool': bool_,
'u8': u8,
@ -347,5 +409,5 @@ LOOKUP_TABLE: Dict[str, Type3] = {
'i64': i64,
'f32': f32,
'f64': f64,
'bytes': bytes,
'bytes': bytes_,
}

View File

@ -5,6 +5,7 @@ from typing import Any, Generator, Iterable, List, TextIO, Union
from phasm import compiler
from phasm.codestyle import phasm_render
from phasm.runtime import calculate_alloc_size
from phasm.type3 import typeclasses as type3classes
from phasm.type3 import types as type3types
from . import runners
@ -82,21 +83,22 @@ class Suite:
wasm_args.append(arg)
continue
if arg_typ is type3types.bytes:
if arg_typ is type3types.bytes_:
adr = _allocate_memory_stored_value(runner, arg_typ, arg)
wasm_args.append(adr)
continue
if isinstance(arg_typ, type3types.AppliedType3):
if arg_typ.base is type3types.static_array:
assert isinstance(arg_typ, type3types.PrimitiveType3)
sa_args = type3types.static_array.did_construct(arg_typ)
if sa_args is not None:
adr = _allocate_memory_stored_value(runner, arg_typ, arg)
wasm_args.append(adr)
continue
if arg_typ.base is type3types.tuple:
adr = _allocate_memory_stored_value(runner, arg_typ, arg)
wasm_args.append(adr)
continue
# if arg_typ.base is type3types.tuple:
# adr = _allocate_memory_stored_value(runner, arg_typ, arg)
# wasm_args.append(adr)
# continue
if isinstance(arg_typ, type3types.StructType3):
adr = _allocate_memory_stored_value(runner, arg_typ, arg)
@ -142,7 +144,7 @@ def _write_memory_stored_value(
val_typ: type3types.Type3,
val: Any,
) -> int:
if val_typ is type3types.bytes:
if val_typ is type3types.bytes_:
adr2 = _allocate_memory_stored_value(runner, val_typ, val)
runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2))
return 4
@ -152,11 +154,11 @@ def _write_memory_stored_value(
runner.interpreter_write_memory(adr, to_write)
return len(to_write)
if isinstance(val_typ, type3types.AppliedType3):
if val_typ.base in (type3types.static_array, type3types.tuple, ):
adr2 = _allocate_memory_stored_value(runner, val_typ, val)
runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2))
return 4
# if isinstance(val_typ, type3types.AppliedType3):
# if val_typ.base in (type3types.static_array, type3types.tuple, ):
# adr2 = _allocate_memory_stored_value(runner, val_typ, val)
# runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2))
# return 4
if isinstance(val_typ, type3types.StructType3):
adr2 = _allocate_memory_stored_value(runner, val_typ, val)
@ -170,7 +172,7 @@ def _allocate_memory_stored_value(
val_typ: type3types.Type3,
val: Any
) -> int:
if val_typ is type3types.bytes:
if val_typ is type3types.bytes_:
assert isinstance(val, bytes)
adr = runner.call('stdlib.types.__alloc_bytes__', len(val))
@ -180,44 +182,42 @@ def _allocate_memory_stored_value(
runner.interpreter_write_memory(adr + 4, val)
return adr
if isinstance(val_typ, type3types.AppliedType3):
if val_typ.base is type3types.static_array:
assert isinstance(val_typ, type3types.PrimitiveType3)
sa_args = type3types.static_array.did_construct(val_typ)
if sa_args is not None:
assert isinstance(val, tuple)
sa_type, sa_len = sa_args
alloc_size = calculate_alloc_size(val_typ)
adr = runner.call('stdlib.alloc.__alloc__', alloc_size)
assert isinstance(adr, int)
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
val_el_typ = val_typ.args[0]
assert not isinstance(val_el_typ, type3types.PlaceholderForType)
tuple_len_obj = val_typ.args[1]
assert isinstance(tuple_len_obj, type3types.IntType3)
tuple_len = tuple_len_obj.value
tuple_len = sa_len.value
assert tuple_len == len(val)
offset = adr
for val_el_val in val:
offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val)
offset += _write_memory_stored_value(runner, offset, sa_type, val_el_val)
return adr
if val_typ.base is type3types.tuple:
assert isinstance(val, tuple)
# if val_typ.base is type3types.tuple:
# assert isinstance(val, tuple)
alloc_size = calculate_alloc_size(val_typ)
adr = runner.call('stdlib.alloc.__alloc__', alloc_size)
assert isinstance(adr, int)
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
# alloc_size = calculate_alloc_size(val_typ)
# adr = runner.call('stdlib.alloc.__alloc__', alloc_size)
# assert isinstance(adr, int)
# sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
assert len(val) == len(val_typ.args)
# assert len(val) == len(val_typ.args)
offset = adr
for val_el_val, val_el_typ in zip(val, val_typ.args):
assert not isinstance(val_el_typ, type3types.PlaceholderForType)
# offset = adr
# for val_el_val, val_el_typ in zip(val, val_typ.args):
# assert not isinstance(val_el_typ, type3types.PlaceholderForType)
offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val)
return adr
# offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val)
# return adr
if isinstance(val_typ, type3types.StructType3):
assert isinstance(val, dict)
@ -278,21 +278,23 @@ def _load_memory_stored_returned_value(
assert isinstance(wasm_value, float), wasm_value
return wasm_value
if ret_type3 is type3types.bytes:
if ret_type3 is type3types.bytes_:
assert isinstance(wasm_value, int), wasm_value
return _load_bytes_from_address(runner, ret_type3, wasm_value)
if isinstance(ret_type3, type3types.AppliedType3):
if ret_type3.base is type3types.static_array:
assert isinstance(ret_type3, type3types.PrimitiveType3) # Type hint
sa_args = type3types.static_array.did_construct(ret_type3)
if sa_args is not None:
assert isinstance(wasm_value, int), wasm_value
return _load_static_array_from_address(runner, ret_type3, wasm_value)
return _load_static_array_from_address(runner, sa_args[0], sa_args[1], wasm_value)
if ret_type3.base is type3types.tuple:
assert isinstance(wasm_value, int), wasm_value
# if ret_type3.base is type3types.tuple:
# assert isinstance(wasm_value, int), wasm_value
return _load_tuple_from_address(runner, ret_type3, wasm_value)
# return _load_tuple_from_address(runner, ret_type3, wasm_value)
if isinstance(ret_type3, type3types.StructType3):
return _load_struct_from_address(runner, ret_type3, wasm_value)
@ -334,23 +336,28 @@ def _unpack(runner: runners.RunnerBase, typ: type3types.Type3, inp: bytes) -> An
assert len(inp) == 8
return struct.unpack('<d', inp)[0]
if typ is type3types.bytes:
if typ is type3types.bytes_:
# Note: For bytes, inp should contain a 4 byte pointer
assert len(inp) == 4
adr = struct.unpack('<I', inp)[0]
return _load_bytes_from_address(runner, typ, adr)
if isinstance(typ, type3types.AppliedType3):
if type3classes.InternalPassAsPointer in typ.classes:
# Note: For applied types, inp should contain a 4 byte pointer
assert len(inp) == 4
adr = struct.unpack('<I', inp)[0]
if typ.base is type3types.static_array:
return _load_static_array_from_address(runner, typ, adr)
assert isinstance(typ, type3types.PrimitiveType3)
if typ.base is type3types.tuple:
return _load_tuple_from_address(runner, typ, adr)
sa_args = type3types.static_array.did_construct(typ)
if sa_args is not None:
sa_type, sa_len = sa_args
return _load_static_array_from_address(runner, sa_type, sa_len, adr)
tp_args = type3types.tuple_.did_construct(typ)
if tp_args is not None:
return _load_tuple_from_address(runner, tp_args, adr)
if isinstance(typ, type3types.StructType3):
# Note: For structs, inp should contain a 4 byte pointer
@ -375,11 +382,8 @@ def _split_read_bytes(all_bytes: bytes, split_sizes: Iterable[int]) -> Generator
yield all_bytes[offset:offset + size]
offset += size
def _load_static_array_from_address(runner: runners.RunnerBase, typ: type3types.AppliedType3, adr: int) -> Any:
sys.stderr.write(f'Reading 0x{adr:08x} {typ:s}\n')
assert 2 == len(typ.args)
sub_typ, len_typ = typ.args
def _load_static_array_from_address(runner: runners.RunnerBase, sub_typ: type3types.PrimitiveType3, len_typ: type3types.IntType3, adr: int) -> Any:
sys.stderr.write(f'Reading 0x{adr:08x} {sub_typ:s} {len_typ:s}\n')
assert not isinstance(sub_typ, type3types.PlaceholderForType)
assert isinstance(len_typ, type3types.IntType3)
@ -396,29 +400,19 @@ def _load_static_array_from_address(runner: runners.RunnerBase, typ: type3types.
for arg_bytes in _split_read_bytes(read_bytes, arg_sizes)
)
def _load_tuple_from_address(runner: runners.RunnerBase, typ: type3types.Type3, adr: int) -> Any:
sys.stderr.write(f'Reading 0x{adr:08x} {typ:s}\n')
assert isinstance(typ, type3types.AppliedType3)
assert typ.base is type3types.tuple
typ_list = [
x
for x in typ.args
if not isinstance(x, type3types.PlaceholderForType)
]
assert len(typ_list) == len(typ.args)
def _load_tuple_from_address(runner: runners.RunnerBase, typ_args: tuple[type3types.PrimitiveType3, ...], adr: int) -> Any:
sys.stderr.write(f'Reading 0x{adr:08x} tuple {len(typ_args)}\n')
arg_sizes = [
calculate_alloc_size(x, is_member=True)
for x in typ_list
for x in typ_args
]
read_bytes = runner.interpreter_read_memory(adr, sum(arg_sizes))
return tuple(
_unpack(runner, arg_typ, arg_bytes)
for arg_typ, arg_bytes in zip(typ_list, _split_read_bytes(read_bytes, arg_sizes))
for arg_typ, arg_bytes in zip(typ_args, _split_read_bytes(read_bytes, arg_sizes))
)
def _load_struct_from_address(runner: runners.RunnerBase, typ: type3types.Type3, adr: int) -> Any:

View File

@ -116,11 +116,11 @@ def testEntry() -> i32:
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
expect_type_error(
'Mismatch between applied types argument count',
'The type of the value returned from function constant should match its return type',
'The type of a tuple is a combination of its members',
)
elif TYPE_NAME.startswith('struct_'):
expect_type_error(
TYPE + ' must be tuple (u32) instead',
TYPE + ' must be (u32) instead',
'The type of the value returned from function constant should match its return type',
)
else:
@ -177,17 +177,17 @@ def testEntry() -> i32:
```py
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
expect_type_error(
'Mismatch between applied types argument count',
'u64[32] must be (u32) instead',
'The type of the value returned from function constant should match its return type',
)
elif TYPE_NAME.startswith('struct_'):
expect_type_error(
TYPE + ' must be tuple (u32) instead',
TYPE + ' must be (u32) instead',
'The type of the value returned from function constant should match its return type',
)
else:
expect_type_error(
TYPE_NAME + ' must be tuple (u32) instead',
TYPE_NAME + ' must be (u32) instead',
'The type of the value returned from function constant should match its return type',
)
```
@ -233,17 +233,17 @@ def select(x: $TYPE) -> (u32, ):
```py
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
expect_type_error(
'Mismatch between applied types argument count',
'u64[32] must be (u32) instead',
'The type of the value returned from function select should match its return type',
)
elif TYPE_NAME.startswith('struct_'):
expect_type_error(
TYPE + ' must be tuple (u32) instead',
TYPE + ' must be (u32) instead',
'The type of the value returned from function select should match its return type',
)
else:
expect_type_error(
TYPE_NAME + ' must be tuple (u32) instead',
TYPE_NAME + ' must be (u32) instead',
'The type of the value returned from function select should match its return type',
)
```
@ -287,11 +287,11 @@ if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
expect_type_error(
'Mismatch between applied types argument count',
# FIXME: Shouldn't this be the same as for the else statement?
'The type of the value passed to argument x of function helper should match the type of that argument',
'The type of a tuple is a combination of its members',
)
elif TYPE_NAME.startswith('struct_'):
expect_type_error(
TYPE + ' must be tuple (u32) instead',
TYPE + ' must be (u32) instead',
'The type of the value passed to argument x of function helper should match the type of that argument',
)
else:
@ -342,17 +342,17 @@ def testEntry() -> i32:
```py
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
expect_type_error(
'Mismatch between applied types argument count',
'u64[32] must be (u32) instead',
'The type of the value passed to argument x of function helper should match the type of that argument',
)
elif TYPE_NAME.startswith('struct_'):
expect_type_error(
TYPE + ' must be tuple (u32) instead',
TYPE + ' must be (u32) instead',
'The type of the value passed to argument x of function helper should match the type of that argument',
)
else:
expect_type_error(
TYPE_NAME + ' must be tuple (u32) instead',
TYPE_NAME + ' must be (u32) instead',
'The type of the value passed to argument x of function helper should match the type of that argument',
)
```