Ideas [skip-ci]
This commit is contained in:
parent
87866cff55
commit
dcdd6aa6eb
8
TODO.md
8
TODO.md
@ -15,9 +15,6 @@
|
|||||||
- 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
|
||||||
|
|
||||||
- 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
|
||||||
@ -36,3 +33,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.
|
||||||
|
|
||||||
|
|
||||||
|
- Subscript
|
||||||
|
- tuple_instantiation?
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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,14 +295,8 @@ 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:
|
return wasm.WasmTypeInt32()
|
||||||
# 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()
|
|
||||||
|
|
||||||
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,15 @@ 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'
|
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 +364,9 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
|||||||
"""
|
"""
|
||||||
Compile: Any expression
|
Compile: Any expression
|
||||||
"""
|
"""
|
||||||
|
if isinstance(inp, ourlang.ConstantTuple):
|
||||||
|
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 +411,20 @@ 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 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):
|
if isinstance(inp.type3, type3types.PrimitiveType3):
|
||||||
expression(wgn, inp.variable.constant)
|
expression(wgn, inp.variable.constant)
|
||||||
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 +440,6 @@ 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 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)
|
||||||
|
|
||||||
raise NotImplementedError(expression, inp.variable)
|
raise NotImplementedError(expression, inp.variable)
|
||||||
@ -479,7 +479,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 +528,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 +566,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 +616,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 +904,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 +985,7 @@ 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'
|
mtyp = 'i32'
|
||||||
else:
|
else:
|
||||||
mtyp = LOAD_STORE_TYPE_MAP.get(mtyp3.name)
|
mtyp = LOAD_STORE_TYPE_MAP.get(mtyp3.name)
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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')
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -48,21 +48,20 @@ 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 less than the length
|
# The offset is outside the allocated bytes
|
||||||
|
g.unreachable(comment='Out of bounds')
|
||||||
|
|
||||||
g.local.get(adr)
|
# The offset is less than the length
|
||||||
g.i32.const(4) # Bytes header
|
|
||||||
g.i32.add()
|
|
||||||
g.local.get(ofs)
|
|
||||||
g.i32.add()
|
|
||||||
g.i32.load8_u()
|
|
||||||
g.return_()
|
|
||||||
|
|
||||||
# The offset is outside the allocated bytes
|
g.local.get(adr)
|
||||||
g.i32.const(0)
|
g.i32.const(4) # Bytes header
|
||||||
|
g.i32.add()
|
||||||
|
g.local.get(ofs)
|
||||||
|
g.i32.add()
|
||||||
|
g.i32.load8_u()
|
||||||
g.return_()
|
g.return_()
|
||||||
|
|
||||||
return i32('return') # To satisfy mypy
|
return i32('return') # To satisfy mypy
|
||||||
|
|||||||
@ -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,6 +162,47 @@ class SameTypeConstraint(ConstraintBase):
|
|||||||
|
|
||||||
return f'SameTypeConstraint({args}, comment={repr(self.comment)})'
|
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):
|
class IntegerCompareConstraint(ConstraintBase):
|
||||||
"""
|
"""
|
||||||
Verifies that the given IntType3 are in order (<=)
|
Verifies that the given IntType3 are in order (<=)
|
||||||
@ -218,7 +223,7 @@ class IntegerCompareConstraint(ConstraintBase):
|
|||||||
prev_val = val_list.pop(0)
|
prev_val = val_list.pop(0)
|
||||||
for next_val in val_list:
|
for next_val in val_list:
|
||||||
if prev_val > next_val:
|
if prev_val > next_val:
|
||||||
return Error(f'{prev_val} must be less or equal than {next_val}')
|
return Error(f'{prev_val} must be less or equal to {next_val}')
|
||||||
|
|
||||||
prev_val = next_val
|
prev_val = next_val
|
||||||
|
|
||||||
@ -292,14 +297,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 +399,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 +407,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 +511,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 (
|
||||||
|
|||||||
@ -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'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
@ -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 = {}
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
if not isinstance(other, AppliedType3):
|
def make_name(self, key: T) -> str:
|
||||||
return False
|
raise NotImplementedError
|
||||||
|
|
||||||
return (
|
def did_construct(self, typ: PrimitiveType3) -> T | None:
|
||||||
self.base == other.base
|
return self._reverse_cache.get(typ)
|
||||||
and len(self.args) == len(other.args)
|
|
||||||
and all(
|
|
||||||
s == x
|
|
||||||
for s, x in zip(self.args, other.args)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def construct(self, key: T) -> PrimitiveType3:
|
||||||
return f'AppliedType3({repr(self.base)}, {repr(self.args)})'
|
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
|
||||||
|
|
||||||
class StructType3(Type3):
|
return result
|
||||||
|
|
||||||
|
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_,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,22 +145,28 @@ 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)
|
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
|
||||||
|
|
||||||
if isinstance(val_typ, type3types.PrimitiveType3):
|
if isinstance(val_typ, type3types.PrimitiveType3):
|
||||||
to_write = WRITE_LOOKUP_MAP[val_typ.name](val)
|
sa_args = type3types.static_array.did_construct(val_typ)
|
||||||
runner.interpreter_write_memory(adr, to_write)
|
if sa_args is not None:
|
||||||
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
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
if isinstance(val_typ, type3types.StructType3):
|
if isinstance(val_typ, type3types.StructType3):
|
||||||
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))
|
||||||
@ -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:
|
||||||
|
|||||||
@ -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:
|
||||||
@ -177,17 +177,17 @@ 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_'):
|
||||||
expect_type_error(
|
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',
|
'The type of the value returned from function constant should match its return type',
|
||||||
)
|
)
|
||||||
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:
|
||||||
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',
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
@ -233,17 +233,17 @@ 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_'):
|
||||||
expect_type_error(
|
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',
|
'The type of the value returned from function select should match its return type',
|
||||||
)
|
)
|
||||||
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 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 +287,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:
|
||||||
@ -342,17 +342,17 @@ 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_'):
|
||||||
expect_type_error(
|
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',
|
'The type of the value passed to argument x of function helper should match the type of that argument',
|
||||||
)
|
)
|
||||||
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:
|
||||||
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',
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|||||||
@ -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')
|
|
||||||
|
|||||||
@ -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 = """
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
116
tests/integration/test_lang/test_subscriptable.py
Normal file
116
tests/integration/test_lang/test_subscriptable.py
Normal 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()
|
||||||
@ -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()
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user