Changes AppliedType to TypeConstructor #7

Merged
jbwdevries merged 1 commits from replace-applied-type-by-type-constructors into master 2025-04-21 09:22:35 +00:00
19 changed files with 473 additions and 479 deletions

13
TODO.md
View File

@ -14,21 +14,19 @@
- Functions don't seem to be a thing on typing level yet? - Functions don't seem to be a thing on typing level yet?
- static_array and tuple should probably not be PrimitiveType3, but instead subclass AppliedType3? - 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 - See if we want to replace Fractional with Real, and add Rational, Irrationl, Algebraic, Transendental
- Does Subscript do what we want? It's a language feature rather a normal typed thing. How would you implement your own Subscript-able type?
- Have a set of rules or guidelines for the constraint comments, they're messy.
- 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 - Find pytest.mark.skip
- There's a weird resolve_as reference in calculate_alloc_size - There's a weird resolve_as reference in calculate_alloc_size
- Either there should be more of them or less - Either there should be more of them or less
- At first glance, looks like failure in the typing system - At first glance, looks like failure in the typing system
- Related to the FIXME in phasm_type3? - Related to the FIXME in phasm_type3?
- Related: Parser is putting stuff in ModuleDataBlock
- WEBASSEMBLY_BUILTIN_BYTES_OPS is special cased - WEBASSEMBLY_BUILTIN_BYTES_OPS is special cased
- Should be part of a prelude (?) - Should be part of a prelude (?)
- In Haskell this is not a type class - In Haskell this is not a type class
- Casting is not implemented except u32 which is special cased - Casting is not implemented except u32 which is special cased
- Parser is putting stuff in ModuleDataBlock
- Compiler should probably do that
- Make prelude more an actual thing - Make prelude more an actual thing
- Merge in type3types.LOOKUP_TABLE - Merge in type3types.LOOKUP_TABLE
- Implemented Bounded: https://hackage.haskell.org/package/base-4.21.0.0/docs/Prelude.html#t:Bounded - Implemented Bounded: https://hackage.haskell.org/package/base-4.21.0.0/docs/Prelude.html#t:Bounded
@ -36,3 +34,8 @@
- Filter out methods that aren't used; other the other way around (easier?) only add __ methods when needed - 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 UnaryOp.operator into type class methods
- Move foldr 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.
- Clean up Subscript implementation - it's half implemented in the compiler. Makes more sense to move more parts to stdlib_types.
- Type constuctor should also be able to constuct placeholders - somehow.
- Make struct a type constructor?
- Add InternalPassAsPointer to Struct?

View File

@ -27,21 +27,6 @@ def type3(inp: Type3OrPlaceholder) -> str:
if inp is type3types.none: if inp is type3types.none:
return '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 return inp.name
def struct_definition(inp: ourlang.StructDefinition) -> str: def struct_definition(inp: ourlang.StructDefinition) -> str:

View File

@ -286,7 +286,7 @@ def type3(inp: type3types.Type3OrPlaceholder) -> wasm.WasmType:
if inp == type3types.f64: if inp == type3types.f64:
return wasm.WasmTypeFloat64() return wasm.WasmTypeFloat64()
if inp == type3types.bytes: if inp == type3types.bytes_:
# bytes are passed as pointer # bytes are passed as pointer
# And pointers are i32 # And pointers are i32
return wasm.WasmTypeInt32() return wasm.WasmTypeInt32()
@ -295,13 +295,7 @@ def type3(inp: type3types.Type3OrPlaceholder) -> wasm.WasmType:
# Structs are passed as pointer, which are i32 # Structs are passed as pointer, which are i32
return wasm.WasmTypeInt32() return wasm.WasmTypeInt32()
if isinstance(inp, type3types.AppliedType3): if type3classes.InternalPassAsPointer in inp.classes:
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
return wasm.WasmTypeInt32() return wasm.WasmTypeInt32()
raise NotImplementedError(type3, inp) raise NotImplementedError(type3, inp)
@ -310,9 +304,21 @@ def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) ->
""" """
Compile: Instantiation (allocation) of a tuple Compile: Instantiation (allocation) of a tuple
""" """
assert isinstance(inp.type3, type3types.AppliedType3) assert isinstance(inp.type3, type3types.PrimitiveType3)
assert inp.type3.base is type3types.tuple
assert len(inp.elements) == len(inp.type3.args) 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 = '' comment_elements = ''
for element in inp.elements: for element in inp.elements:
@ -329,14 +335,17 @@ def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) ->
# Store each element individually # Store each element individually
offset = 0 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): if isinstance(exp_type3, type3types.PlaceholderForType):
assert exp_type3.resolve_as is not None assert exp_type3.resolve_as is not None
assert isinstance(exp_type3.resolve_as, type3types.PrimitiveType3)
exp_type3 = exp_type3.resolve_as exp_type3 = exp_type3.resolve_as
assert element.type3 == exp_type3 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'
elif isinstance(exp_type3, type3types.StructType3):
mtyp = 'i32' mtyp = 'i32'
else: else:
assert isinstance(exp_type3, type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs') assert isinstance(exp_type3, type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs')
@ -357,6 +366,10 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
""" """
Compile: Any expression Compile: Any expression
""" """
if isinstance(inp, (ourlang.ConstantStruct, ourlang.ConstantTuple, )):
# These are implemented elsewhere
raise Exception
if isinstance(inp, ourlang.ConstantPrimitive): if isinstance(inp, ourlang.ConstantPrimitive):
assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
@ -401,11 +414,16 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
if isinstance(inp.variable, ourlang.ModuleConstantDef): if isinstance(inp.variable, ourlang.ModuleConstantDef):
assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
if isinstance(inp.type3, type3types.PrimitiveType3): if type3classes.InternalPassAsPointer in inp.type3.classes:
expression(wgn, inp.variable.constant) # 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 return
if inp.type3 is type3types.bytes: if inp.type3 is type3types.bytes_:
assert isinstance(inp.variable.constant, ourlang.ConstantBytes) assert isinstance(inp.variable.constant, ourlang.ConstantBytes)
address = inp.variable.constant.data_block.address address = inp.variable.constant.data_block.address
@ -421,25 +439,10 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
wgn.i32.const(address) wgn.i32.const(address)
return return
if isinstance(inp.type3, type3types.AppliedType3): if isinstance(inp.type3, type3types.PrimitiveType3):
if inp.type3.base == type3types.static_array: expression(wgn, inp.variable.constant)
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 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)
raise NotImplementedError(expression, inp.variable) raise NotImplementedError(expression, inp.variable)
@ -479,7 +482,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
if inp.type3 == type3types.u32: if inp.type3 == type3types.u32:
if inp.operator == 'len': if inp.operator == 'len':
if inp.right.type3 == type3types.bytes: if inp.right.type3 == type3types.bytes_:
wgn.i32.load() wgn.i32.load()
return return
@ -528,19 +531,17 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
if isinstance(inp, ourlang.Subscript): if isinstance(inp, ourlang.Subscript):
assert isinstance(inp.varref.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR 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.varref)
expression(wgn, inp.index) expression(wgn, inp.index)
wgn.call(stdlib_types.__subscript_bytes__) wgn.call(stdlib_types.__subscript_bytes__)
return return
if isinstance(inp.varref.type3, type3types.AppliedType3): assert isinstance(inp.varref.type3, type3types.PrimitiveType3)
if inp.varref.type3.base == type3types.static_array:
assert 2 == len(inp.varref.type3.args) sa_args = type3types.static_array.did_construct(inp.varref.type3)
el_type = inp.varref.type3.args[0] if sa_args is not None:
assert isinstance(el_type, type3types.Type3) el_type, el_len = sa_args
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 # 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 # and we don't need to do the out of bounds check
@ -568,24 +569,24 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
wgn.add_statement(f'{mtyp}.load') wgn.add_statement(f'{mtyp}.load')
return return
if inp.varref.type3.base == type3types.tuple: tp_args = type3types.tuple_.did_construct(inp.varref.type3)
if tp_args is not None:
assert isinstance(inp.index, ourlang.ConstantPrimitive) assert isinstance(inp.index, ourlang.ConstantPrimitive)
assert isinstance(inp.index.value, int) assert isinstance(inp.index.value, int)
offset = 0 offset = 0
for el_type in inp.varref.type3.args[0:inp.index.value]: for el_type in tp_args[0:inp.index.value]:
assert isinstance(el_type, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR assert isinstance(el_type, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
offset += calculate_alloc_size(el_type) offset += calculate_alloc_size(el_type)
# This doubles as the out of bounds check el_type = tp_args[inp.index.value]
el_type = inp.varref.type3.args[inp.index.value]
assert isinstance(el_type, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR assert isinstance(el_type, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
expression(wgn, inp.varref) expression(wgn, inp.varref)
if isinstance(el_type, type3types.StructType3): if isinstance(el_type, type3types.StructType3):
mtyp = 'i32' mtyp = 'i32'
elif isinstance(el_type, type3types.AppliedType3) and el_type.base is type3types.tuple: elif type3classes.InternalPassAsPointer in el_type.classes:
mtyp = 'i32' mtyp = 'i32'
else: else:
assert isinstance(el_type, type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs') assert isinstance(el_type, type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs')
@ -618,7 +619,7 @@ def expression_fold(wgn: WasmGenerator, inp: ourlang.Fold) -> None:
""" """
assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR 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) raise NotImplementedError(expression_fold, inp, inp.iter.type3)
wgn.add_statement('nop', comment='acu :: u8') wgn.add_statement('nop', comment='acu :: u8')
@ -906,7 +907,7 @@ def module_data(inp: ourlang.ModuleData) -> bytes:
data_list.append(module_data_f64(constant.value)) data_list.append(module_data_f64(constant.value))
continue continue
if constant.type3 == type3types.bytes: if constant.type3 == type3types.bytes_:
assert isinstance(constant, ourlang.ConstantBytes) assert isinstance(constant, ourlang.ConstantBytes)
assert isinstance(constant.value, bytes) assert isinstance(constant.value, bytes)
data_list.append(module_data_u32(len(constant.value))) data_list.append(module_data_u32(len(constant.value)))
@ -987,7 +988,9 @@ def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstruc
# Store each member individually # Store each member individually
for memname, mtyp3 in inp.struct_type3.members.items(): for memname, mtyp3 in inp.struct_type3.members.items():
mtyp: Optional[str] mtyp: Optional[str]
if isinstance(mtyp3, type3types.StructType3) or isinstance(mtyp3, type3types.AppliedType3): if type3classes.InternalPassAsPointer in mtyp3.classes:
mtyp = 'i32'
elif isinstance(mtyp3, type3types.StructType3):
mtyp = 'i32' mtyp = 'i32'
else: else:
mtyp = LOAD_STORE_TYPE_MAP.get(mtyp3.name) mtyp = LOAD_STORE_TYPE_MAP.get(mtyp3.name)

View File

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

View File

@ -664,7 +664,7 @@ class OurVisitor:
raise NotImplementedError(f'{node.value} as constant') 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 isinstance(node, ast.Constant):
if node.value is None: if node.value is None:
return type3types.none return type3types.none
@ -693,18 +693,17 @@ class OurVisitor:
if not isinstance(node.ctx, ast.Load): if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context') _raise_static_error(node, 'Must be load context')
return type3types.AppliedType3( return type3types.static_array(
type3types.static_array, self.visit_type(module, node.value),
[self.visit_type(module, node.value), type3types.IntType3(node.slice.value)], type3types.IntType3(node.slice.value),
) )
if isinstance(node, ast.Tuple): if isinstance(node, ast.Tuple):
if not isinstance(node.ctx, ast.Load): if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context') _raise_static_error(node, 'Must be load context')
return type3types.AppliedType3( return type3types.tuple_(
type3types.tuple, *(self.visit_type(module, elt) for elt in node.elts)
(self.visit_type(module, elt) for elt in node.elts)
) )
raise NotImplementedError(f'{node} as type') 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, ): if typ in (type3types.u64, type3types.i64, type3types.f64, ):
return 8 return 8
if typ == type3types.bytes: if typ == type3types.bytes_:
if is_member: if is_member:
return 4 return 4
@ -27,28 +27,30 @@ def calculate_alloc_size(typ: type3types.Type3, is_member: bool = False) -> int:
for x in typ.members.values() for x in typ.members.values()
) )
if isinstance(typ, type3types.AppliedType3): assert isinstance(typ, type3types.PrimitiveType3)
if typ.base is type3types.static_array:
sa_args = type3types.static_array.did_construct(typ)
if sa_args is not None:
if is_member: if is_member:
# tuples referred to by other structs or tuples are pointers # tuples referred to by other structs or tuples are pointers
return 4 return 4
assert isinstance(typ.args[0], type3types.Type3) sa_type, sa_len = sa_args
assert isinstance(typ.args[1], type3types.IntType3)
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: if is_member:
# tuples referred to by other structs or tuples are pointers # tuples referred to by other structs or tuples are pointers
return 4 return 4
size = 0 size = 0
for arg in typ.args: for arg in tp_args:
assert not isinstance(arg, type3types.IntType3) assert not isinstance(arg, type3types.IntType3)
if isinstance(arg, type3types.PlaceholderForType): if isinstance(arg, type3types.PlaceholderForType):
assert arg.resolve_as is not None assert isinstance(arg.resolve_as, type3types.PrimitiveType3)
arg = arg.resolve_as arg = arg.resolve_as
size += calculate_alloc_size(arg, is_member=True) size += calculate_alloc_size(arg, is_member=True)

View File

@ -48,9 +48,12 @@ def __subscript_bytes__(g: Generator, adr: i32, ofs: i32) -> i32:
g.local.get(ofs) g.local.get(ofs)
g.local.get(adr) g.local.get(adr)
g.i32.load() g.i32.load()
g.i32.lt_u() g.i32.ge_u()
with g.if_(): with g.if_():
# The offset is outside the allocated bytes
g.unreachable(comment='Out of bounds')
# The offset is less than the length # The offset is less than the length
g.local.get(adr) g.local.get(adr)
@ -61,10 +64,6 @@ def __subscript_bytes__(g: Generator, adr: i32, ofs: i32) -> i32:
g.i32.load8_u() g.i32.load8_u()
g.return_() g.return_()
# The offset is outside the allocated bytes
g.i32.const(0)
g.return_()
return i32('return') # To satisfy mypy return i32('return') # To satisfy mypy
@func_wrapper() @func_wrapper()

View File

@ -112,10 +112,6 @@ class SameTypeConstraint(ConstraintBase):
known_types.append(typ) known_types.append(typ)
continue continue
if isinstance(typ, types.AppliedType3):
known_types.append(typ)
continue
if isinstance(typ, types.PlaceholderForType): if isinstance(typ, types.PlaceholderForType):
if typ.resolve_as is not None: if typ.resolve_as is not None:
known_types.append(typ.resolve_as) known_types.append(typ.resolve_as)
@ -132,38 +128,6 @@ class SameTypeConstraint(ConstraintBase):
first_type = known_types[0] first_type = known_types[0]
for typ in known_types[1:]: 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: if typ != first_type:
return Error(f'{typ:s} must be {first_type:s} instead', comment=self.comment) return Error(f'{typ:s} must be {first_type:s} instead', comment=self.comment)
@ -198,45 +162,46 @@ class SameTypeConstraint(ConstraintBase):
return f'SameTypeConstraint({args}, comment={repr(self.comment)})' return f'SameTypeConstraint({args}, comment={repr(self.comment)})'
class IntegerCompareConstraint(ConstraintBase): class TupleMatchConstraint(ConstraintBase):
""" def __init__(self, exp_type: types.Type3OrPlaceholder, args: List[types.Type3OrPlaceholder], comment: str):
Verifies that the given IntType3 are in order (<=)
"""
__slots__ = ('int_type3_list', )
int_type3_list: List[types.IntType3]
def __init__(self, *int_type3: types.IntType3, comment: Optional[str] = None) -> None:
super().__init__(comment=comment) super().__init__(comment=comment)
assert len(int_type3) > 1 self.exp_type = exp_type
self.int_type3_list = [*int_type3] self.args = list(args)
def check(self) -> CheckResult: def check(self) -> CheckResult:
val_list = [x.value for x in self.int_type3_list] exp_type = self.exp_type
if isinstance(exp_type, types.PlaceholderForType):
if exp_type.resolve_as is None:
return RequireTypeSubstitutes()
prev_val = val_list.pop(0) exp_type = exp_type.resolve_as
for next_val in val_list:
if prev_val > next_val:
return Error(f'{prev_val} must be less or equal than {next_val}')
prev_val = next_val assert isinstance(exp_type, types.PrimitiveType3)
return None sa_args = types.static_array.did_construct(exp_type)
if sa_args is not None:
sa_type, sa_len = sa_args
def human_readable(self) -> HumanReadableRet: if sa_len.value != len(self.args):
return ( return Error('Mismatch between applied types argument count', comment=self.comment)
' <= '.join('{t' + str(idx) + '}' for idx in range(len(self.int_type3_list))),
{
't' + str(idx): typ
for idx, typ in enumerate(self.int_type3_list)
},
)
def __repr__(self) -> str: return [
args = ', '.join(repr(x) for x in self.int_type3_list) SameTypeConstraint(arg, sa_type)
for arg in self.args
]
return f'IntegerCompareConstraint({args}, comment={repr(self.comment)})' 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 CastableConstraint(ConstraintBase): class CastableConstraint(ConstraintBase):
""" """
@ -292,14 +257,7 @@ class MustImplementTypeClassConstraint(ConstraintBase):
type3: types.Type3OrPlaceholder type3: types.Type3OrPlaceholder
DATA = { DATA = {
'u8': {'BitWiseOperation', 'EqualComparison', 'StrictPartialOrder'},
'u32': {'BitWiseOperation', 'EqualComparison', 'StrictPartialOrder'},
'u64': {'BitWiseOperation', 'EqualComparison', 'StrictPartialOrder'},
'i32': {'EqualComparison', 'StrictPartialOrder'},
'i64': {'EqualComparison', 'StrictPartialOrder'},
'bytes': {'Foldable', 'Sized'}, 'bytes': {'Foldable', 'Sized'},
'f32': {'Fractional', 'FloatingPoint'},
'f64': {'Fractional', 'FloatingPoint'},
} }
def __init__(self, type_class3: Union[str, typeclasses.Type3Class], type3: types.Type3OrPlaceholder, comment: Optional[str] = None) -> None: def __init__(self, type_class3: Union[str, typeclasses.Type3Class], type3: types.Type3OrPlaceholder, comment: Optional[str] = None) -> None:
@ -401,7 +359,7 @@ class LiteralFitsConstraint(ConstraintBase):
return Error('Must be real', comment=self.comment) # FIXME: Add line information 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): if isinstance(self.literal.value, bytes):
return None return None
@ -409,45 +367,47 @@ class LiteralFitsConstraint(ConstraintBase):
res: NewConstraintList res: NewConstraintList
if isinstance(self.type3, types.AppliedType3): assert isinstance(self.type3, types.PrimitiveType3)
if self.type3.base == types.tuple:
tp_args = types.tuple_.did_construct(self.type3)
if tp_args is not None:
if not isinstance(self.literal, ourlang.ConstantTuple): if not isinstance(self.literal, ourlang.ConstantTuple):
return Error('Must be tuple', comment=self.comment) 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) return Error('Tuple element count mismatch', comment=self.comment)
res = [] res = []
res.extend( res.extend(
LiteralFitsConstraint(x, y) 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( res.extend(
SameTypeConstraint(x, y.type3) 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 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): if not isinstance(self.literal, ourlang.ConstantTuple):
return Error('Must be tuple', comment=self.comment) return Error('Must be tuple', comment=self.comment)
assert 2 == len(self.type3.args) sa_type, sa_len = sa_args
assert isinstance(self.type3.args[1], types.IntType3)
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) return Error('Member count mismatch', comment=self.comment)
res = [] res = []
res.extend( res.extend(
LiteralFitsConstraint(self.type3.args[0], y) LiteralFitsConstraint(sa_type, y)
for y in self.literal.value for y in self.literal.value
) )
res.extend( res.extend(
SameTypeConstraint(self.type3.args[0], y.type3) SameTypeConstraint(sa_type, y.type3)
for y in self.literal.value for y in self.literal.value
) )
@ -511,57 +471,58 @@ class CanBeSubscriptedConstraint(ConstraintBase):
self.index_type3 = index.type3 self.index_type3 = index.type3
def check(self) -> CheckResult: def check(self) -> CheckResult:
if isinstance(self.type3, types.PlaceholderForType): exp_type = self.type3
if self.type3.resolve_as is None: if isinstance(exp_type, types.PlaceholderForType):
if exp_type.resolve_as is None:
return RequireTypeSubstitutes() return RequireTypeSubstitutes()
self.type3 = self.type3.resolve_as 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 isinstance(self.type3, types.AppliedType3):
if self.type3.base == types.static_array:
result: List[ConstraintBase] = [ result: List[ConstraintBase] = [
SameTypeConstraint(types.u32, self.index_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'), 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'), SameTypeConstraint(sa_type, self.ret_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'),
] ]
if isinstance(self.index, ourlang.ConstantPrimitive): if isinstance(self.index, ourlang.ConstantPrimitive):
assert isinstance(self.index.value, int) assert isinstance(self.index.value, int)
assert isinstance(self.type3.args[1], types.IntType3)
result.append( if self.index.value < 0 or sa_len.value <= self.index.value:
IntegerCompareConstraint( return Error('Tuple index out of range')
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: # We special case tuples to allow for ease of use to the programmer
# e.g. rather than having to do `fst a` and `snd a` and only have to-sized tuples
# we use a[0] and a[1] and allow for a[2] and on.
tp_args = types.tuple_.did_construct(exp_type)
if tp_args is not None:
if not isinstance(self.index, ourlang.ConstantPrimitive): if not isinstance(self.index, ourlang.ConstantPrimitive):
return Error('Must index with literal') return Error('Must index with literal')
if not isinstance(self.index.value, int): if not isinstance(self.index.value, int):
return Error('Must index with integer literal') return Error('Must index with integer literal')
if self.index.value < 0 or len(self.type3.args) <= self.index.value: if self.index.value < 0 or len(tp_args) <= self.index.value:
return Error('Tuple index out of range') return Error('Tuple index out of range')
return [ return [
SameTypeConstraint(types.u32, self.index_type3, comment=f'Tuple subscript index {self.index.value}'), 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}'), SameTypeConstraint(tp_args[self.index.value], self.ret_type3, comment=f'Tuple subscript index {self.index.value}'),
] ]
if self.type3 is types.bytes: if exp_type is types.bytes_:
return [ return [
SameTypeConstraint(types.u32, self.index_type3, comment='([]) :: bytes -> u32 -> u8'), SameTypeConstraint(types.u32, self.index_type3, comment='([]) :: bytes -> u32 -> u8'),
SameTypeConstraint(types.u8, self.ret_type3, comment='([]) :: bytes -> u32 -> u8'), SameTypeConstraint(types.u8, self.ret_type3, comment='([]) :: bytes -> u32 -> u8'),
] ]
if self.type3.name in types.LOOKUP_TABLE: return Error(f'{exp_type.name} cannot be subscripted')
return Error(f'{self.type3.name} cannot be subscripted')
raise NotImplementedError(self.type3)
def human_readable(self) -> HumanReadableRet: def human_readable(self) -> HumanReadableRet:
return ( return (

View File

@ -16,6 +16,7 @@ from .constraints import (
LiteralFitsConstraint, LiteralFitsConstraint,
MustImplementTypeClassConstraint, MustImplementTypeClassConstraint,
SameTypeConstraint, SameTypeConstraint,
TupleMatchConstraint,
) )
ConstraintGenerator = Generator[ConstraintBase, None, None] ConstraintGenerator = Generator[ConstraintBase, None, None]
@ -142,9 +143,9 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator:
yield from expression(ctx, arg) yield from expression(ctx, arg)
r_type.append(arg.type3) r_type.append(arg.type3)
yield SameTypeConstraint( yield TupleMatchConstraint(
inp.type3, inp.type3,
type3types.AppliedType3(type3types.tuple, r_type), r_type,
comment='The type of a tuple is a combination of its members' 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 .constraintsgenerator import phasm_type3_generate_constraints
from .types import ( from .types import (
AppliedType3,
IntType3, IntType3,
PlaceholderForType, PlaceholderForType,
PrimitiveType3, 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) placeholder_id_map[placeholder_id] = 'T' + str(len(placeholder_id_map) + 1)
return placeholder_id_map[placeholder_id] 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) raise NotImplementedError(inp)
def print_constraint_list(placeholder_id_map: Dict[int, str], constraint_list: List[ConstraintBase], placeholder_substitutes: SubstitutionMap) -> None: 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: def __repr__(self) -> str:
return self.name return self.name
InternalPassAsPointer = Type3Class('InternalPassAsPointer', ['a'], methods={}, operators={})
Eq = Type3Class('Eq', ['a'], methods={}, operators={ Eq = Type3Class('Eq', ['a'], methods={}, operators={
'==': 'a -> a -> bool', '==': 'a -> a -> bool',
'!=': '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 These are actual, instantiated types; not the abstract types that the
constraint generator works with. 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 ( from .typeclasses import (
Bits, Bits,
@ -12,6 +24,7 @@ from .typeclasses import (
Floating, Floating,
Fractional, Fractional,
Integral, Integral,
InternalPassAsPointer,
IntNum, IntNum,
NatNum, NatNum,
Ord, Ord,
@ -83,7 +96,7 @@ class Type3:
return not self.__eq__(other) return not self.__eq__(other)
def __hash__(self) -> int: def __hash__(self) -> int:
raise NotImplementedError return hash(self.name)
def __bool__(self) -> bool: def __bool__(self) -> bool:
raise NotImplementedError raise NotImplementedError
@ -98,6 +111,9 @@ class PrimitiveType3(Type3):
class IntType3(Type3): class IntType3(Type3):
""" """
Sometimes you can have an int as type, e.g. when using static arrays 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', ) __slots__ = ('value', )
@ -107,7 +123,6 @@ class IntType3(Type3):
def __init__(self, value: int) -> None: def __init__(self, value: int) -> None:
super().__init__(str(value), []) super().__init__(str(value), [])
assert 0 <= value
self.value = value self.value = value
def __eq__(self, other: Any) -> bool: def __eq__(self, other: Any) -> bool:
@ -119,6 +134,9 @@ class IntType3(Type3):
raise NotImplementedError raise NotImplementedError
def __hash__(self) -> int:
return hash(self.value)
class PlaceholderForType: class PlaceholderForType:
""" """
A placeholder type, for when we don't know the final type yet A placeholder type, for when we don't know the final type yet
@ -166,64 +184,100 @@ class PlaceholderForType:
Type3OrPlaceholder = Union[Type3, PlaceholderForType] Type3OrPlaceholder = Union[Type3, PlaceholderForType]
class AppliedType3(Type3): T = TypeVar('T')
"""
A Type3 that has been applied to another type
"""
__slots__ = ('base', 'args', )
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: type_classes: Set[Type3Class]
args = [*args] """
assert args, 'Must at least one argument' The type classes that the constructed types implement
"""
super().__init__( _cache: dict[T, PrimitiveType3]
base.name """
+ ' (' When constructing a type with the same arguments,
+ ') ('.join(str(x) for x in args) # FIXME: Do we need to redo the name on substitution? it should produce the exact same result.
+ ')', """
[]
)
self.base = base _reverse_cache: dict[PrimitiveType3, T]
self.args = args """
Sometimes we need to know the key that created a type.
"""
@property def __init__(self, name: str, classes: Iterable[Type3Class], type_classes: Iterable[Type3Class]) -> None:
def has_placeholders(self) -> bool: self.name = name
return any( self.classes = set(classes)
isinstance(x, PlaceholderForType) self.type_classes = set(type_classes)
for x in self.args
)
def __eq__(self, other: Any) -> bool: self._cache = {}
if not isinstance(other, Type3): self._reverse_cache = {}
def make_name(self, key: T) -> str:
raise NotImplementedError raise NotImplementedError
if not isinstance(other, AppliedType3): def did_construct(self, typ: PrimitiveType3) -> T | None:
return False return self._reverse_cache.get(typ)
return ( def construct(self, key: T) -> PrimitiveType3:
self.base == other.base result = self._cache.get(key, None)
and len(self.args) == len(other.args) if result is None:
and all( self._cache[key] = result = PrimitiveType3(self.make_name(key), self.type_classes)
s == x self._reverse_cache[result] = key
for s, x in zip(self.args, other.args)
)
)
def __repr__(self) -> str: return result
return f'AppliedType3({repr(self.base)}, {repr(self.args)})'
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 A Type3 struct with named members
""" """
@ -315,20 +369,27 @@ f64 = PrimitiveType3('f64', [Eq, Floating, Fractional, IntNum, NatNum, Ord])
A 32-bits IEEE 754 float, of 64 bits width. 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. 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 It should be applied with one argument. It has a runtime-dynamic length
of the same type repeated. of the same type repeated.
""" """
tuple = PrimitiveType3('tuple', []) # pylint: disable=W0622 tuple_ = TypeConstructor_Tuple('tuple', [], [
InternalPassAsPointer,
])
""" """
This is a fixed length piece of memory. This is a fixed length piece of memory.
@ -336,7 +397,7 @@ It should be applied with zero or more arguments. It has a compile time
determined length, and each argument can be different. determined length, and each argument can be different.
""" """
LOOKUP_TABLE: Dict[str, Type3] = { LOOKUP_TABLE: Dict[str, PrimitiveType3] = {
'none': none, 'none': none,
'bool': bool_, 'bool': bool_,
'u8': u8, 'u8': u8,
@ -347,5 +408,5 @@ LOOKUP_TABLE: Dict[str, Type3] = {
'i64': i64, 'i64': i64,
'f32': f32, 'f32': f32,
'f64': f64, '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 import compiler
from phasm.codestyle import phasm_render from phasm.codestyle import phasm_render
from phasm.runtime import calculate_alloc_size from phasm.runtime import calculate_alloc_size
from phasm.type3 import typeclasses as type3classes
from phasm.type3 import types as type3types from phasm.type3 import types as type3types
from . import runners from . import runners
@ -82,18 +83,20 @@ class Suite:
wasm_args.append(arg) wasm_args.append(arg)
continue continue
if arg_typ is type3types.bytes: if arg_typ is type3types.bytes_:
adr = _allocate_memory_stored_value(runner, arg_typ, arg) adr = _allocate_memory_stored_value(runner, arg_typ, arg)
wasm_args.append(adr) wasm_args.append(adr)
continue continue
if isinstance(arg_typ, type3types.AppliedType3): assert isinstance(arg_typ, type3types.PrimitiveType3)
if arg_typ.base is type3types.static_array: sa_args = type3types.static_array.did_construct(arg_typ)
if sa_args is not None:
adr = _allocate_memory_stored_value(runner, arg_typ, arg) adr = _allocate_memory_stored_value(runner, arg_typ, arg)
wasm_args.append(adr) wasm_args.append(adr)
continue continue
if arg_typ.base is type3types.tuple: tp_args = type3types.tuple_.did_construct(arg_typ)
if tp_args is not None:
adr = _allocate_memory_stored_value(runner, arg_typ, arg) adr = _allocate_memory_stored_value(runner, arg_typ, arg)
wasm_args.append(adr) wasm_args.append(adr)
continue continue
@ -103,7 +106,7 @@ class Suite:
wasm_args.append(adr) wasm_args.append(adr)
continue continue
raise NotImplementedError(arg) raise NotImplementedError(arg_typ, arg)
write_header(sys.stderr, 'Memory (pre run)') write_header(sys.stderr, 'Memory (pre run)')
runner.interpreter_dump_memory(sys.stderr) runner.interpreter_dump_memory(sys.stderr)
@ -142,18 +145,7 @@ def _write_memory_stored_value(
val_typ: type3types.Type3, val_typ: type3types.Type3,
val: Any, val: Any,
) -> int: ) -> 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
if isinstance(val_typ, type3types.PrimitiveType3):
to_write = WRITE_LOOKUP_MAP[val_typ.name](val)
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) adr2 = _allocate_memory_stored_value(runner, val_typ, val)
runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2)) runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2))
return 4 return 4
@ -163,6 +155,23 @@ def _write_memory_stored_value(
runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2)) runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2))
return 4 return 4
if isinstance(val_typ, type3types.PrimitiveType3):
sa_args = type3types.static_array.did_construct(val_typ)
if sa_args is not None:
adr2 = _allocate_memory_stored_value(runner, val_typ, val)
runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2))
return 4
tp_args = type3types.tuple_.did_construct(val_typ)
if tp_args is not None:
adr2 = _allocate_memory_stored_value(runner, val_typ, val)
runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2))
return 4
to_write = WRITE_LOOKUP_MAP[val_typ.name](val)
runner.interpreter_write_memory(adr, to_write)
return len(to_write)
raise NotImplementedError(val_typ, val) raise NotImplementedError(val_typ, val)
def _allocate_memory_stored_value( def _allocate_memory_stored_value(
@ -170,7 +179,7 @@ def _allocate_memory_stored_value(
val_typ: type3types.Type3, val_typ: type3types.Type3,
val: Any val: Any
) -> int: ) -> int:
if val_typ is type3types.bytes: if val_typ is type3types.bytes_:
assert isinstance(val, bytes) assert isinstance(val, bytes)
adr = runner.call('stdlib.types.__alloc_bytes__', len(val)) adr = runner.call('stdlib.types.__alloc_bytes__', len(val))
@ -180,29 +189,30 @@ def _allocate_memory_stored_value(
runner.interpreter_write_memory(adr + 4, val) runner.interpreter_write_memory(adr + 4, val)
return adr return adr
if isinstance(val_typ, type3types.AppliedType3): assert isinstance(val_typ, type3types.PrimitiveType3)
if val_typ.base is type3types.static_array: sa_args = type3types.static_array.did_construct(val_typ)
if sa_args is not None:
assert isinstance(val, tuple) assert isinstance(val, tuple)
sa_type, sa_len = sa_args
alloc_size = calculate_alloc_size(val_typ) alloc_size = calculate_alloc_size(val_typ)
adr = runner.call('stdlib.alloc.__alloc__', alloc_size) adr = runner.call('stdlib.alloc.__alloc__', alloc_size)
assert isinstance(adr, int) assert isinstance(adr, int)
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n') sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
val_el_typ = val_typ.args[0] tuple_len = sa_len.value
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
assert tuple_len == len(val) assert tuple_len == len(val)
offset = adr offset = adr
for val_el_val in val: 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 return adr
if val_typ.base is type3types.tuple: val_el_typ: type3types.Type3
tp_args = type3types.tuple_.did_construct(val_typ)
if tp_args is not None:
assert isinstance(val, tuple) assert isinstance(val, tuple)
alloc_size = calculate_alloc_size(val_typ) alloc_size = calculate_alloc_size(val_typ)
@ -210,10 +220,10 @@ def _allocate_memory_stored_value(
assert isinstance(adr, int) assert isinstance(adr, int)
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n') sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
assert len(val) == len(val_typ.args) assert len(val) == len(tp_args)
offset = adr offset = adr
for val_el_val, val_el_typ in zip(val, val_typ.args): for val_el_val, val_el_typ in zip(val, tp_args):
assert not isinstance(val_el_typ, type3types.PlaceholderForType) assert not isinstance(val_el_typ, type3types.PlaceholderForType)
offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val) offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val)
@ -278,21 +288,24 @@ def _load_memory_stored_returned_value(
assert isinstance(wasm_value, float), wasm_value assert isinstance(wasm_value, float), wasm_value
return wasm_value return wasm_value
if ret_type3 is type3types.bytes: if ret_type3 is type3types.bytes_:
assert isinstance(wasm_value, int), wasm_value assert isinstance(wasm_value, int), wasm_value
return _load_bytes_from_address(runner, ret_type3, wasm_value) return _load_bytes_from_address(runner, ret_type3, wasm_value)
if isinstance(ret_type3, type3types.AppliedType3): assert isinstance(ret_type3, type3types.PrimitiveType3) # Type hint
if ret_type3.base is type3types.static_array:
sa_args = type3types.static_array.did_construct(ret_type3)
if sa_args is not None:
assert isinstance(wasm_value, int), wasm_value 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: tp_args = type3types.tuple_.did_construct(ret_type3)
if tp_args is not None:
assert isinstance(wasm_value, int), wasm_value assert isinstance(wasm_value, int), wasm_value
return _load_tuple_from_address(runner, ret_type3, wasm_value) return _load_tuple_from_address(runner, tp_args, wasm_value)
if isinstance(ret_type3, type3types.StructType3): if isinstance(ret_type3, type3types.StructType3):
return _load_struct_from_address(runner, ret_type3, wasm_value) return _load_struct_from_address(runner, ret_type3, wasm_value)
@ -334,23 +347,28 @@ def _unpack(runner: runners.RunnerBase, typ: type3types.Type3, inp: bytes) -> An
assert len(inp) == 8 assert len(inp) == 8
return struct.unpack('<d', inp)[0] 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 # Note: For bytes, inp should contain a 4 byte pointer
assert len(inp) == 4 assert len(inp) == 4
adr = struct.unpack('<I', inp)[0] adr = struct.unpack('<I', inp)[0]
return _load_bytes_from_address(runner, typ, adr) 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 # Note: For applied types, inp should contain a 4 byte pointer
assert len(inp) == 4 assert len(inp) == 4
adr = struct.unpack('<I', inp)[0] adr = struct.unpack('<I', inp)[0]
if typ.base is type3types.static_array: assert isinstance(typ, type3types.PrimitiveType3)
return _load_static_array_from_address(runner, typ, adr)
if typ.base is type3types.tuple: sa_args = type3types.static_array.did_construct(typ)
return _load_tuple_from_address(runner, typ, adr) 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): if isinstance(typ, type3types.StructType3):
# Note: For structs, inp should contain a 4 byte pointer # Note: For structs, inp should contain a 4 byte pointer
@ -375,11 +393,8 @@ def _split_read_bytes(all_bytes: bytes, split_sizes: Iterable[int]) -> Generator
yield all_bytes[offset:offset + size] yield all_bytes[offset:offset + size]
offset += size offset += size
def _load_static_array_from_address(runner: runners.RunnerBase, typ: type3types.AppliedType3, adr: int) -> Any: 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} {typ:s}\n') sys.stderr.write(f'Reading 0x{adr:08x} {sub_typ:s} {len_typ:s}\n')
assert 2 == len(typ.args)
sub_typ, len_typ = typ.args
assert not isinstance(sub_typ, type3types.PlaceholderForType) assert not isinstance(sub_typ, type3types.PlaceholderForType)
assert isinstance(len_typ, type3types.IntType3) assert isinstance(len_typ, type3types.IntType3)
@ -396,29 +411,19 @@ def _load_static_array_from_address(runner: runners.RunnerBase, typ: type3types.
for arg_bytes in _split_read_bytes(read_bytes, arg_sizes) 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: def _load_tuple_from_address(runner: runners.RunnerBase, typ_args: tuple[type3types.PrimitiveType3, ...], adr: int) -> Any:
sys.stderr.write(f'Reading 0x{adr:08x} {typ:s}\n') sys.stderr.write(f'Reading 0x{adr:08x} tuple {len(typ_args)}\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)
arg_sizes = [ arg_sizes = [
calculate_alloc_size(x, is_member=True) 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)) read_bytes = runner.interpreter_read_memory(adr, sum(arg_sizes))
return tuple( return tuple(
_unpack(runner, arg_typ, arg_bytes) _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: 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_'): if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
expect_type_error( expect_type_error(
'Mismatch between applied types argument count', '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_'): elif TYPE_NAME.startswith('struct_'):
expect_type_error( 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', 'The type of the value returned from function constant should match its return type',
) )
else: else:
@ -175,19 +175,14 @@ def testEntry() -> i32:
``` ```
```py ```py
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'): if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('struct_'):
expect_type_error( expect_type_error(
'Mismatch between applied types argument count', TYPE + ' 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',
'The type of the value returned from function constant should match its return type', 'The type of the value returned from function constant should match its return type',
) )
else: else:
expect_type_error( 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', 'The type of the value returned from function constant should match its return type',
) )
``` ```
@ -231,19 +226,14 @@ def select(x: $TYPE) -> (u32, ):
``` ```
```py ```py
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'): if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('struct_'):
expect_type_error( expect_type_error(
'Mismatch between applied types argument count', TYPE + ' 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',
'The type of the value returned from function select should match its return type', 'The type of the value returned from function select should match its return type',
) )
else: else:
expect_type_error( 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', 'The type of the value returned from function select should match its return type',
) )
``` ```
@ -287,11 +277,11 @@ if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
expect_type_error( expect_type_error(
'Mismatch between applied types argument count', 'Mismatch between applied types argument count',
# FIXME: Shouldn't this be the same as for the else statement? # 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_'): elif TYPE_NAME.startswith('struct_'):
expect_type_error( 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', 'The type of the value passed to argument x of function helper should match the type of that argument',
) )
else: else:
@ -340,19 +330,14 @@ def testEntry() -> i32:
``` ```
```py ```py
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'): if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('struct_'):
expect_type_error( expect_type_error(
'Mismatch between applied types argument count', TYPE + ' 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',
'The type of the value passed to argument x of function helper should match the type of that argument', 'The type of the value passed to argument x of function helper should match the type of that argument',
) )
else: else:
expect_type_error( 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', 'The type of the value passed to argument x of function helper should match the type of that argument',
) )
``` ```

View File

@ -1,7 +1,5 @@
import pytest import pytest
from phasm.type3.entry import Type3Exception
from ..helpers import Suite from ..helpers import Suite
@ -16,38 +14,3 @@ def testEntry(f: bytes) -> u32:
result = Suite(code_py).run_code(b'This yet is another test') result = Suite(code_py).run_code(b'This yet is another test')
assert 24 == result.returned_value assert 24 == result.returned_value
@pytest.mark.integration_test
def test_bytes_index_ok():
code_py = """
@exported
def testEntry(f: bytes) -> u8:
return f[8]
"""
result = Suite(code_py).run_code(b'This is another test')
assert 0x61 == result.returned_value
@pytest.mark.integration_test
def test_bytes_index_out_of_bounds():
code_py = """
@exported
def testEntry(f: bytes, g: bytes) -> u8:
return f[50]
"""
result = Suite(code_py).run_code(b'Short', b'Long' * 100)
assert 0 == result.returned_value
@pytest.mark.integration_test
def test_bytes_index_invalid_type():
code_py = """
@exported
def testEntry(f: bytes) -> u64:
return f[50]
"""
with pytest.raises(Type3Exception, match=r'u64 must be u8 instead'):
Suite(code_py).run_code(b'Short')

View File

@ -1,73 +1,10 @@
import pytest import pytest
import wasmtime
from phasm.type3.entry import Type3Exception from phasm.type3.entry import Type3Exception
from ..helpers import Suite from ..helpers import Suite
@pytest.mark.integration_test
def test_static_array_index_ok():
code_py = """
@exported
def testEntry(f: u64[3]) -> u64:
return f[2]
"""
result = Suite(code_py).run_code((1, 2, 3, ))
assert 3 == result.returned_value
@pytest.mark.integration_test
def test_static_array_index_invalid_type():
code_py = """
@exported
def testEntry(f: f32[3]) -> u64:
return f[0]
"""
with pytest.raises(Type3Exception, match=r'u64 must be f32 instead'):
Suite(code_py).run_code((0.0, 1.5, 2.25, ))
@pytest.mark.integration_test
def test_module_constant_type_mismatch_not_subscriptable():
code_py = """
CONSTANT: u8 = 24
@exported
def testEntry() -> u8:
return CONSTANT[0]
"""
with pytest.raises(Type3Exception, match='u8 cannot be subscripted'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_module_constant_type_mismatch_index_out_of_range_constant():
code_py = """
CONSTANT: u8[3] = (24, 57, 80, )
@exported
def testEntry() -> u8:
return CONSTANT[3]
"""
with pytest.raises(Type3Exception, match='3 must be less or equal than 2'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_module_constant_type_mismatch_index_out_of_range_variable():
code_py = """
CONSTANT: u8[3] = (24, 57, 80, )
@exported
def testEntry(x: u32) -> u8:
return CONSTANT[x]
"""
with pytest.raises(wasmtime.Trap):
Suite(code_py).run_code(3)
@pytest.mark.integration_test @pytest.mark.integration_test
def test_static_array_constant_too_few_values(): def test_static_array_constant_too_few_values():
code_py = """ code_py = """

View File

@ -74,5 +74,5 @@ def testEntry(arg: Struct) -> (i32, i32, ):
return arg.param return arg.param
""" """
with pytest.raises(Type3Exception, match=type_ + r' must be tuple \(i32\) \(i32\) instead'): with pytest.raises(Type3Exception, match=type_ + r' must be \(i32, i32, \) instead'):
Suite(code_py).run_code() Suite(code_py).run_code()

View File

@ -0,0 +1,116 @@
import pytest
import wasmtime
from phasm.type3.entry import Type3Exception
from ..helpers import Suite
@pytest.mark.integration_test
@pytest.mark.parametrize('type_, in_put, exp_result', [
('(u8, u8, )', (45, 46), 45, ),
('u8[2]', (45, 46), 45, ),
('bytes', b'This is a test', 84)
])
def test_subscript_0(type_, in_put, exp_result):
code_py = f"""
@exported
def testEntry(f: {type_}) -> u8:
return f[0]
"""
result = Suite(code_py).run_code(in_put)
assert exp_result == result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('type_, in_put, exp_result', [
('(u8, u8, )', (45, 46), 45, ),
('u8[2]', (45, 46), 45, ),
('bytes', b'This is a test', 84)
])
def test_subscript_invalid_type(type_, in_put, exp_result):
code_py = f"""
@exported
def testEntry(f: {type_}) -> u32:
return f[0]
"""
with pytest.raises(Type3Exception, match='u32 must be u8 instead'):
Suite(code_py).run_code(in_put)
@pytest.mark.integration_test
def test_subscript_tuple_must_be_literal():
code_py = """
@exported
def testEntry(x: (u8, u32, u64), y: u8) -> u64:
return x[y]
"""
with pytest.raises(Type3Exception, match='Must index with literal'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_subscript_tuple_must_be_int():
code_py = """
@exported
def testEntry(x: (u8, u32, u64)) -> u64:
return x[0.0]
"""
with pytest.raises(Type3Exception, match='Must index with integer literal'):
Suite(code_py).run_code()
@pytest.mark.integration_test
@pytest.mark.parametrize('type_, in_put', [
('(u8, u8, )', (45, 46), ),
('u8[2]', (45, 46), ),
# bytes isn't known at runtime so works like normal
])
def test_subscript_oob_constant_low(type_, in_put):
code_py = f"""
@exported
def testEntry(x: {type_}) -> u8:
return x[-1]
"""
with pytest.raises(Type3Exception, match='Tuple index out of range'):
Suite(code_py).run_code(in_put)
@pytest.mark.integration_test
def test_subscript_oob_constant_high():
code_py = """
@exported
def testEntry(x: (u8, u32, u64)) -> u64:
return x[4]
"""
with pytest.raises(Type3Exception, match='Tuple index out of range'):
Suite(code_py).run_code()
@pytest.mark.integration_test
@pytest.mark.parametrize('type_, in_put', [
# Cannot Subscript tuple without a constant
('u8[2]', (45, 46), ),
('bytes', b'This is a test', ),
])
def test_subscript_oob_normal(type_, in_put):
code_py = f"""
@exported
def testEntry(x: {type_}, y: u32) -> u8:
return x[y]
"""
with pytest.raises(wasmtime.Trap):
Suite(code_py).run_code(in_put, 255)
@pytest.mark.integration_test
def test_subscript_not_subscriptable():
code_py = """
@exported
def testEntry(x: u8) -> u8:
return x[0]
"""
with pytest.raises(Type3Exception, match='u8 cannot be subscripted'):
Suite(code_py).run_code()

View File

@ -70,25 +70,3 @@ CONSTANT: (u32, u8, u8, ) = (24, 4000, 1, )
with pytest.raises(Type3Exception, match=r'Must fit in 1 byte\(s\)'): with pytest.raises(Type3Exception, match=r'Must fit in 1 byte\(s\)'):
Suite(code_py).run_code() Suite(code_py).run_code()
@pytest.mark.integration_test
def test_tuple_must_use_literal_for_indexing():
code_py = """
@exported
def testEntry(x: (u8, u32, u64), y: u8) -> u64:
return x[y]
"""
with pytest.raises(Type3Exception, match='Must index with literal'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_tuple_must_use_integer_for_indexing():
code_py = """
@exported
def testEntry(x: (u8, u32, u64)) -> u64:
return x[0.0]
"""
with pytest.raises(Type3Exception, match='Must index with integer literal'):
Suite(code_py).run_code()