From b3bad54bb6713bc9d966517fda142b824b0df9f2 Mon Sep 17 00:00:00 2001 From: Burak Bilge Yalcinkaya Date: Fri, 3 Apr 2026 10:26:15 +0300 Subject: [PATCH 01/15] update `DataDefn` and `ExternDefn`, fix type annotations --- .../src/pykwasm/kdist/wasm-semantics/test.md | 26 ++++---- .../pykwasm/kdist/wasm-semantics/wasm-text.md | 14 ++--- .../src/pykwasm/kdist/wasm-semantics/wasm.md | 19 ++++-- pykwasm/src/pykwasm/kwasm_ast.py | 59 +++++++++++++++---- pykwasm/src/pykwasm/wasm2kast.py | 34 +++++++++-- 5 files changed, 107 insertions(+), 45 deletions(-) diff --git a/pykwasm/src/pykwasm/kdist/wasm-semantics/test.md b/pykwasm/src/pykwasm/kdist/wasm-semantics/test.md index 06266dac5..26f726dc4 100644 --- a/pykwasm/src/pykwasm/kdist/wasm-semantics/test.md +++ b/pykwasm/src/pykwasm/kdist/wasm-semantics/test.md @@ -321,19 +321,19 @@ The conformance tests contain imports of the `"spectest"` module. start: .EmptyStmts , importDefns: .EmptyStmts , exports: - #export (... name: #token("\"global_i32\"" , "WasmStringToken") , index: 0 ) - #export (... name: #token("\"global_i64\"" , "WasmStringToken") , index: 1 ) - #export (... name: #token("\"global_f32\"" , "WasmStringToken") , index: 2 ) - #export (... name: #token("\"global_f64\"" , "WasmStringToken") , index: 3 ) - #export (... name: #token("\"table\"" , "WasmStringToken") , index: 0 ) - #export (... name: #token("\"memory\"" , "WasmStringToken") , index: 0 ) - #export (... name: #token("\"print\"" , "WasmStringToken") , index: 0 ) - #export (... name: #token("\"print_i32\"" , "WasmStringToken") , index: 1 ) - #export (... name: #token("\"print_i64\"" , "WasmStringToken") , index: 2 ) - #export (... name: #token("\"print_f32\"" , "WasmStringToken") , index: 3 ) - #export (... name: #token("\"print_f64\"" , "WasmStringToken") , index: 4 ) - #export (... name: #token("\"print_i32_f32\"" , "WasmStringToken") , index: 5 ) - #export (... name: #token("\"print_f64_f64\"" , "WasmStringToken") , index: 6 ) + #export (... name: #token("\"global_i32\"" , "WasmStringToken") , index: #externIdxGlobal(0) ) + #export (... name: #token("\"global_i64\"" , "WasmStringToken") , index: #externIdxGlobal(1) ) + #export (... name: #token("\"global_f32\"" , "WasmStringToken") , index: #externIdxGlobal(2) ) + #export (... name: #token("\"global_f64\"" , "WasmStringToken") , index: #externIdxGlobal(3) ) + #export (... name: #token("\"table\"" , "WasmStringToken") , index: #externIdxTable(0) ) + #export (... name: #token("\"memory\"" , "WasmStringToken") , index: #externIdxMemory(0) ) + #export (... name: #token("\"print\"" , "WasmStringToken") , index: #externIdxFunc(0) ) + #export (... name: #token("\"print_i32\"" , "WasmStringToken") , index: #externIdxFunc(1) ) + #export (... name: #token("\"print_i64\"" , "WasmStringToken") , index: #externIdxFunc(2) ) + #export (... name: #token("\"print_f32\"" , "WasmStringToken") , index: #externIdxFunc(3) ) + #export (... name: #token("\"print_f64\"" , "WasmStringToken") , index: #externIdxFunc(4) ) + #export (... name: #token("\"print_i32_f32\"" , "WasmStringToken") , index: #externIdxFunc(5) ) + #export (... name: #token("\"print_f64_f64\"" , "WasmStringToken") , index: #externIdxFunc(6) ) .EmptyStmts , metadata: #meta (... id: , funcIds: .Map , filename: .String ) ) diff --git a/pykwasm/src/pykwasm/kdist/wasm-semantics/wasm-text.md b/pykwasm/src/pykwasm/kdist/wasm-semantics/wasm-text.md index 4b4b3e233..3c259aad5 100644 --- a/pykwasm/src/pykwasm/kdist/wasm-semantics/wasm-text.md +++ b/pykwasm/src/pykwasm/kdist/wasm-semantics/wasm-text.md @@ -998,19 +998,19 @@ Wasm currently supports only one table, so we do not need to resolve any identif Wasm currently supports only one memory, so we do not need to resolve any identifiers. ```k - rule #t2aDefn(( data _:Index (offset IS) DS )) => #data(0, #t2aInstrs(IS), #DS2Bytes(DS)) + rule #t2aDefn(( data _:Index (offset IS) DS )) => #data(#DS2Bytes(DS), #active(0, #t2aInstrs(IS))) ``` #### Exports ```k - rule #t2aDefn(( export ENAME ( func IDENT:Identifier ) )) => #export(ENAME, {IDS[IDENT]}:>Int) requires IDENT in_keys(IDS) - rule #t2aDefn(( export ENAME ( global IDENT:Identifier ) )) => #export(ENAME, {IDS[IDENT]}:>Int) requires IDENT in_keys(IDS) - rule #t2aDefn<_>(( export ENAME ( func I:Int ) )) => #export(ENAME, I) - rule #t2aDefn<_>(( export ENAME ( global I:Int ) )) => #export(ENAME, I) + rule #t2aDefn(( export ENAME ( func IDENT:Identifier ) )) => #export(ENAME, #externIdxFunc({IDS[IDENT]}:>Int)) requires IDENT in_keys(IDS) + rule #t2aDefn(( export ENAME ( global IDENT:Identifier ) )) => #export(ENAME, #externIdxGlobal({IDS[IDENT]}:>Int)) requires IDENT in_keys(IDS) + rule #t2aDefn<_>(( export ENAME ( func I:Int ) )) => #export(ENAME, #externIdxFunc(I)) + rule #t2aDefn<_>(( export ENAME ( global I:Int ) )) => #export(ENAME, #externIdxGlobal(I)) - rule #t2aDefn<_>(( export ENAME ( table _ ) )) => #export(ENAME, 0) - rule #t2aDefn<_>(( export ENAME ( memory _ ) )) => #export(ENAME, 0) + rule #t2aDefn<_>(( export ENAME ( table _ ) )) => #export(ENAME, #externIdxTable(0)) + rule #t2aDefn<_>(( export ENAME ( memory _ ) )) => #export(ENAME, #externIdxMemory(0)) ``` #### Other Definitions diff --git a/pykwasm/src/pykwasm/kdist/wasm-semantics/wasm.md b/pykwasm/src/pykwasm/kdist/wasm-semantics/wasm.md index 9ae1404df..80f97ec1c 100644 --- a/pykwasm/src/pykwasm/kdist/wasm-semantics/wasm.md +++ b/pykwasm/src/pykwasm/kdist/wasm-semantics/wasm.md @@ -1762,11 +1762,14 @@ Memories can be initialized with data, specified as a list of bytes together wit The `data` initializer simply puts these bytes into the specified memory, starting at the offset. ```k - syntax DataDefn ::= #data(index : Int, offset : Instrs, data : Bytes) [symbol(aDataDefn)] - | "data" "{" Int Bytes "}" + syntax DataDefn ::= #data(data: Bytes, mode: DataMode) [symbol(aDataDefn)] + syntax DataMode ::= #active(memidx: Int, offset: Instrs) [symbol(aDataModeActive)] + | "#passive" [symbol(aDataModePassive)] // -------------------------------------------- - // Default to memory 0. - rule #data(IDX, IS, DATA) => sequenceInstrs(IS) ~> data { IDX DATA } ... + syntax KItem ::= "data" "{" Int Bytes "}" + + rule #data(DATA, #active(IDX, IS)) => sequenceInstrs(IS) ~> data { IDX DATA } ... + rule #data(_DATA, #passive) => .K ... rule data { MEMIDX DSBYTES } => trap ... < i32 > OFFSET : _STACK @@ -1783,7 +1786,6 @@ The `data` initializer simply puts these bytes into the specified memory, starti requires OFFSET +Int lengthBytes(DSBYTES) >Int SIZE *Int #pageSize() - // For now, deal only with memory 0. rule data { MEMIDX DSBYTES } => .K ... < i32 > OFFSET : STACK => STACK CUR @@ -1828,7 +1830,12 @@ Export Exports make functions, tables, memories and globals available for importing into other modules. ```k - syntax ExportDefn ::= #export(name : WasmString, index : Int) [symbol(aExportDefn)] + syntax ExportDefn ::= #export(name : WasmString, index : ExternIdx) [symbol(aExportDefn)] + syntax ExternIdx ::= #externIdxFunc(Int) [symbol(aExternIdxFunc)] + | #externIdxTable(Int) [symbol(aExternIdxTable)] + | #externIdxMemory(Int) [symbol(aExternIdxMemory)] + | #externIdxGlobal(Int) [symbol(aExternIdxGlobal)] + | #externIdxTag(Int) [symbol(aExternIdxTag)] syntax Alloc ::= ExportDefn // --------------------------- rule #export(ENAME, IDX) => .K ... diff --git a/pykwasm/src/pykwasm/kwasm_ast.py b/pykwasm/src/pykwasm/kwasm_ast.py index a818e0a35..a0480e8a8 100644 --- a/pykwasm/src/pykwasm/kwasm_ast.py +++ b/pykwasm/src/pykwasm/kwasm_ast.py @@ -53,6 +53,8 @@ ELEM = 'aElemDefn' DATA = 'aDataDefn' +DATAMODE_ACTIVE = 'aDataModeActive' +DATAMODE_PASSIVE = 'aDataModePassive' START = 'aStartDefn' @@ -164,6 +166,9 @@ def idx_to_ref(idx: int | None) -> KInner: MUT_CONST = KApply('mutConst', []) MUT_VAR = KApply('mutVar', []) +HEAPTYPE_FUNC = KApply('func', []) +HEAPTYPE_EXTERN = KApply('extern', []) + def vec_type(valtypes: KInner) -> KInner: return KApply(VEC_TYPE, [valtypes]) @@ -173,7 +178,7 @@ def func_type(params: KInner, results: KInner) -> KInner: return KApply(FUNC_TYPE, [params, results]) -def limits(tup: tuple[int, int]) -> KInner: +def limits(tup: tuple[int, int | None]) -> KInner: i = tup[0] j = tup[1] if j is None: @@ -228,9 +233,9 @@ def CALL(function_idx: int) -> KInner: return KApply('aCall', [KInt(function_idx)]) -def CALL_INDIRECT(type_idx: int) -> KInner: +def CALL_INDIRECT(table_idx: int, type_idx: int) -> KInner: type_use = KApply('aTypeUseIndex', [KInt(type_idx)]) - return KApply('aCall_indirect', [KInt(0), type_use]) + return KApply('aCall_indirect', [KInt(table_idx), type_use]) ########################## @@ -245,8 +250,8 @@ def REF_FUNC(func_idx: int) -> KInner: REF_IS_NULL: KInner = KApply('aRef.is_null', []) -def REF_NULL(t: str) -> KInner: - return KApply('aRef.null', [KApply(t, [])]) +def REF_NULL(heaptype: KInner) -> KInner: + return KApply('aRef.null', [heaptype]) ########################## @@ -626,11 +631,11 @@ def global_desc(global_type: KInner, id: KInner = EMPTY_ID) -> KInner: return KApply(GLOBAL_DESC, [id, global_type]) -def table_desc(lim: tuple[int, int], id: KInner = EMPTY_ID) -> KInner: +def table_desc(lim: tuple[int, int | None], id: KInner = EMPTY_ID) -> KInner: return KApply(TABLE_DESC, [id, limits(lim)]) -def memory_desc(lim: tuple[int, int], id: KInner = EMPTY_ID) -> KInner: +def memory_desc(lim: tuple[int, int | None], id: KInner = EMPTY_ID) -> KInner: return KApply(MEMORY_DESC, [id, limits(lim)]) @@ -647,11 +652,11 @@ def func(type: KInner, locals: KInner, body: KInner, metadata: KInner = EMPTY_FU return KApply(FUNC, [type, locals, body, metadata]) -def table(lim: tuple[int, int], typ: KInner, metadata: KInner = EMPTY_ID) -> KInner: +def table(lim: tuple[int, int | None], typ: KInner, metadata: KInner = EMPTY_ID) -> KInner: return KApply(TABLE, [limits(lim), typ, metadata]) -def memory(lim: tuple[int, int], metadata: KInner = EMPTY_ID) -> KInner: +def memory(lim: tuple[int, int | None], metadata: KInner = EMPTY_ID) -> KInner: return KApply(MEMORY, [limits(lim), metadata]) @@ -675,8 +680,16 @@ def elem(typ: KInner, elem_mode: KInner, init: Iterable[int | None], metadata: K return KApply(ELEM, [typ, refs(init), elem_mode, metadata]) -def data(memory_idx: int, offset: KInner, data: bytes) -> KInner: - return KApply(DATA, [KInt(memory_idx), offset, KBytes(data)]) +def data(bs: bytes, mode: KInner) -> KInner: + return KApply(DATA, [KBytes(bs), mode]) + + +def datamode_active(memidx: int, offset: KInner) -> KInner: + return KApply(DATAMODE_ACTIVE, [KInt(memidx), offset]) + + +def datamode_passive() -> KInner: + return KApply(DATAMODE_PASSIVE, []) def start(start_idx: int) -> KInner: @@ -687,8 +700,28 @@ def imp(mod_name: KInner, name: KInner, import_desc: KInner) -> KInner: return KApply(IMPORT, [mod_name, name, import_desc]) -def export(name: KInner, index: int) -> KInner: - return KApply(EXPORT, [name, KInt(index)]) +def export(name: KInner, index: KInner) -> KInner: + return KApply(EXPORT, [name, index]) + + +def externidx_func(i: int) -> KInner: + return KApply('aExternIdxFunc', [KInt(i)]) + + +def externidx_table(i: int) -> KInner: + return KApply('aExternIdxTable', [KInt(i)]) + + +def externidx_memory(i: int) -> KInner: + return KApply('aExternIdxMemory', [KInt(i)]) + + +def externidx_global(i: int) -> KInner: + return KApply('aExternIdxGlobal', [KInt(i)]) + + +def externidx_tag(i: int) -> KInner: + return KApply('aExternIdxTag', [KInt(i)]) def module_metadata(mid: None = None, fids: None = None, filename: str | None = None) -> KInner: diff --git a/pykwasm/src/pykwasm/wasm2kast.py b/pykwasm/src/pykwasm/wasm2kast.py index 91cea1ff6..d899f346e 100644 --- a/pykwasm/src/pykwasm/wasm2kast.py +++ b/pykwasm/src/pykwasm/wasm2kast.py @@ -10,7 +10,19 @@ from typing import TYPE_CHECKING from wasm import instructions -from wasm.datatypes import GlobalType, MemoryType, Mutability, TableType, TypeIdx, ValType, addresses +from wasm.datatypes import ( + FunctionIdx, + GlobalIdx, + GlobalType, + MemoryIdx, + MemoryType, + Mutability, + TableIdx, + TableType, + TypeIdx, + ValType, + addresses, +) from wasm.datatypes.element_segment import ElemModeActive, ElemModeDeclarative, ElemModePassive from wasm.opcodes import BinaryOpcode from wasm.parsers import parse_module @@ -162,7 +174,8 @@ def elem(e: ElementSegment): def data(d: DataSegment): offset = instrs(d.offset) - return a.data(d.memory_idx, offset, d.init) + mode = a.datamode_active(d.memory_idx, offset) + return a.data(d.init, mode) def start(s: StartFunction): @@ -188,7 +201,16 @@ def imp(i: Import): def export(e: Export): name = a.wasm_string(e.name) - idx = e.desc + if isinstance(e.desc, FunctionIdx): + idx = a.externidx_func(e.desc) + elif isinstance(e.desc, GlobalIdx): + idx = a.externidx_global(e.desc) + elif isinstance(e.desc, MemoryIdx): + idx = a.externidx_memory(e.desc) + elif isinstance(e.desc, TableIdx): + idx = a.externidx_table(e.desc) + else: + raise ValueError(f'Invalid extern index: {e.desc}') return a.export(name, idx) @@ -227,7 +249,7 @@ def instr(i): if i.opcode == B.CALL: return a.CALL(i.function_idx) if i.opcode == B.CALL_INDIRECT: - return a.CALL_INDIRECT(i.type_idx) + return a.CALL_INDIRECT(i.table_idx, i.type_idx) if i.opcode == B.ELSE: raise (ValueError('ELSE opcode: should have been filtered out.')) if i.opcode == B.END: @@ -321,9 +343,9 @@ def instr(i): return a.REF_FUNC(i.funcidx) if isinstance(i, instructions.RefNull): if i.reftype is addresses.FunctionAddress: - return a.REF_NULL('func') + return a.REF_NULL(a.HEAPTYPE_FUNC) if i.reftype is addresses.ExternAddress: - return a.REF_NULL('extern') + return a.REF_NULL(a.HEAPTYPE_EXTERN) raise ValueError(f'Unknown heap type: {i}, {i.reftype}') if isinstance(i, instructions.TableGet): return a.TABLE_GET(i.tableidx) From 111360377871cf3541474d189eaa68b8bd364316 Mon Sep 17 00:00:00 2001 From: Burak Bilge Yalcinkaya Date: Fri, 3 Apr 2026 14:09:46 +0300 Subject: [PATCH 02/15] add binary parser for numbers --- pykwasm/src/pykwasm/binary/floats.py | 21 +++++ pykwasm/src/pykwasm/binary/integers.py | 75 +++++++++++++++++ pykwasm/src/pykwasm/binary/utils.py | 53 ++++++++++++ pykwasm/src/tests/unit/test_binary_parser.py | 86 ++++++++++++++++++++ 4 files changed, 235 insertions(+) create mode 100644 pykwasm/src/pykwasm/binary/floats.py create mode 100644 pykwasm/src/pykwasm/binary/integers.py create mode 100644 pykwasm/src/pykwasm/binary/utils.py create mode 100644 pykwasm/src/tests/unit/test_binary_parser.py diff --git a/pykwasm/src/pykwasm/binary/floats.py b/pykwasm/src/pykwasm/binary/floats.py new file mode 100644 index 000000000..de1dd5359 --- /dev/null +++ b/pykwasm/src/pykwasm/binary/floats.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +import struct +from typing import TYPE_CHECKING + +from .utils import read_bytes + +if TYPE_CHECKING: + from .utils import InputStream + + +def f32(s: InputStream) -> float: + bs = read_bytes(4, s) + f = struct.unpack(' float: + bs = read_bytes(8, s) + f = struct.unpack(' int: + n = read_byte(s) + + # No continuation bit + if n < 2**7: + if n >= 2**bits: + raise WasmParseError(f'Value {n} exceeds u{bits} range') + return n + + # Has continuation bit + if bits <= 7: # Not enough bits remaining + raise WasmParseError(f'LEB128 encoding too long for u{bits}') + + m = parse_uint(bits - 7, s) + return (n - 2**7) + (2**7) * m + + +def parse_sint(bits: int, s: InputStream) -> int: + n = read_byte(s) + + # No continuation bit and 0 sign bit + if n < 2**6: + if n >= 2 ** (bits - 1): + raise WasmParseError(f'Value {n} exceeds s{bits} range') + return n + + # No continuation bit and 1 sign bit + if 2**6 <= n and n < 2**7: + signed_value = n - 2**7 + if signed_value < -(2 ** (bits - 1)): + raise WasmParseError(f'Value {signed_value} exceeds s{bits} range') + return n - 2**7 + + # Has continuation bit + if bits <= 7: # Not enough bits remaining + raise WasmParseError(f'LEB128 encoding too long for s{bits}') + + i = parse_sint(bits - 7, s) + return (2**7) * i + (n - 2**7) + + +def u32(s: InputStream) -> int: + return parse_uint(32, s) + + +def u64(s: InputStream) -> int: + return parse_uint(64, s) + + +def to_uninterpreted(bits: int, x: int) -> int: + if 0 <= x: + return x + return x + 2 ** bits + +def parse_iint(bits: int, s: InputStream) -> int: + i = parse_sint(bits, s) + return to_uninterpreted(bits, i) + + +def i32(s: InputStream) -> int: + return parse_iint(32, s) + + +def i64(s: InputStream) -> int: + return parse_iint(64, s) diff --git a/pykwasm/src/pykwasm/binary/utils.py b/pykwasm/src/pykwasm/binary/utils.py new file mode 100644 index 000000000..4efa5d44d --- /dev/null +++ b/pykwasm/src/pykwasm/binary/utils.py @@ -0,0 +1,53 @@ +from typing import BinaryIO, Callable, TypeAlias, TypeVar + +InputStream: TypeAlias = BinaryIO + +A = TypeVar('A') +Parser: TypeAlias = Callable[[InputStream], A] +NParser: TypeAlias = Callable[[int, InputStream], A] + + +def read_bytes(n: int, s: InputStream) -> bytes: + b = s.read(n) + if len(b) != n: + raise WasmEOFError(f'Unexpected EOF. Expected {n} bytes, got {len(b)}') + return b + + +def read_byte(s: InputStream) -> int: + return read_bytes(1, s)[0] + + +def peek_bytes(n: int, s: InputStream) -> bytes: + pos = s.tell() + b = s.read(n) + if len(b) != n: + raise WasmEOFError(f'Unexpected EOF. Expected {n} bytes, got {len(b)}') + reset(pos, s) + return b + + +def peek_byte(s: InputStream) -> int: + return peek_bytes(1, s)[0] + + +def skip(n: int, s: InputStream) -> None: + s.seek(n, 1) + + +def reset(n: int, s: InputStream) -> None: + s.seek(n, 0) + + +def expect_bytes(expected: bytes, s: InputStream) -> None: + bs = read_bytes(len(expected), s) + if bs != expected: + raise WasmParseError(f'Expected {expected!r}, got {bs!r}') + + +class WasmParseError(Exception): + pass + + +class WasmEOFError(WasmParseError): + pass diff --git a/pykwasm/src/tests/unit/test_binary_parser.py b/pykwasm/src/tests/unit/test_binary_parser.py new file mode 100644 index 000000000..f1f469356 --- /dev/null +++ b/pykwasm/src/tests/unit/test_binary_parser.py @@ -0,0 +1,86 @@ +import io +import struct +import pytest + +from pykwasm.binary.utils import WasmParseError, WasmEOFError +from pykwasm.binary import floats, integers + +def stream(data: bytes) -> io.BytesIO: + """Helper: wrap bytes in a seekable stream.""" + return io.BytesIO(data) + + +class TestFloats: + VALUES = [0.0, 3.14, -1.5, 1.23456789, -9.99, float('inf'), float('-inf')] + + @pytest.mark.parametrize("value", VALUES) + def test_f32(self, value): + encoded = struct.pack(' bytes: + buf = [] + while True: + b = value & 0x7F + value >>= 7 + if value: + buf.append(b | 0x80) + else: + buf.append(b) + break + return bytes(buf) + + @staticmethod + def encode_sleb128(value: int) -> bytes: + buf = [] + while True: + b = value & 0x7F + value >>= 7 + if (value == 0 and b & 0x40 == 0) or (value == -1 and b & 0x40 != 0): + buf.append(b) + break + buf.append(b | 0x80) + return bytes(buf) + + @pytest.mark.parametrize("value", U32_VALUES) + def test_unsigned_32(self, value): + encoded = self.encode_uleb128(value) + assert integers.u32(stream(encoded)) == value + + @pytest.mark.parametrize("value", U64_VALUES) + def test_unsigned_64(self, value): + encoded = self.encode_uleb128(value) + assert integers.u64(stream(encoded)) == value + + @pytest.mark.parametrize("value", I32_VALUES) + def test_uninterpreted_32(self, value): + expected = integers.to_uninterpreted(32, value) + encoded = self.encode_sleb128(value) + assert integers.i32(stream(encoded)) == expected + + @pytest.mark.parametrize("value", I64_VALUES) + def test_uninterpreted_64(self, value): + expected = integers.to_uninterpreted(64, value) + encoded = self.encode_sleb128(value) + assert integers.i64(stream(encoded)) == expected \ No newline at end of file From e3dc7c9880b4980fdc7387f0e112819be6504d9f Mon Sep 17 00:00:00 2001 From: Burak Bilge Yalcinkaya Date: Fri, 3 Apr 2026 14:12:02 +0300 Subject: [PATCH 03/15] parse modules --- pykwasm/src/pykwasm/binary/__init__.py | 0 pykwasm/src/pykwasm/binary/combinators.py | 53 ++ pykwasm/src/pykwasm/binary/indices.py | 69 +++ pykwasm/src/pykwasm/binary/instructions.py | 541 +++++++++++++++++++++ pykwasm/src/pykwasm/binary/module.py | 345 +++++++++++++ pykwasm/src/pykwasm/binary/types.py | 204 ++++++++ 6 files changed, 1212 insertions(+) create mode 100644 pykwasm/src/pykwasm/binary/__init__.py create mode 100644 pykwasm/src/pykwasm/binary/combinators.py create mode 100644 pykwasm/src/pykwasm/binary/indices.py create mode 100644 pykwasm/src/pykwasm/binary/instructions.py create mode 100644 pykwasm/src/pykwasm/binary/module.py create mode 100644 pykwasm/src/pykwasm/binary/types.py diff --git a/pykwasm/src/pykwasm/binary/__init__.py b/pykwasm/src/pykwasm/binary/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pykwasm/src/pykwasm/binary/combinators.py b/pykwasm/src/pykwasm/binary/combinators.py new file mode 100644 index 000000000..0adc46731 --- /dev/null +++ b/pykwasm/src/pykwasm/binary/combinators.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from .integers import u32 +from .utils import WasmParseError, reset + +if TYPE_CHECKING: + from .utils import A, InputStream, Parser + + +def iterate(f: Parser[A], s: InputStream) -> list[A]: + results = [] + while True: + try: + results.append(f(s)) + except (WasmParseError, IndexError, ValueError): + break + return results + + +def sized(p: Parser[A], s: InputStream) -> A: + size = u32(s) + start_pos = s.tell() + res = p(s) + end_pos = s.tell() + if end_pos - start_pos != size: + raise WasmParseError('Size mismatch') + return res + + +def parse_n(p: Parser[A], n: int, s: InputStream) -> list[A]: + results = [] + for _ in range(n): + x = p(s) + results.append(x) + return results + + +def list_of(p: Parser[A], s: InputStream) -> list[A]: + n = u32(s) + return parse_n(p, n, s) + + +def either(ps: list[Parser[A]], s: InputStream) -> A: + for p in ps: + pos = s.tell() + try: + return p(s) + except WasmParseError: + reset(pos, s) + continue + raise WasmParseError('None of the alternatives succeeded') diff --git a/pykwasm/src/pykwasm/binary/indices.py b/pykwasm/src/pykwasm/binary/indices.py new file mode 100644 index 000000000..2e8d62628 --- /dev/null +++ b/pykwasm/src/pykwasm/binary/indices.py @@ -0,0 +1,69 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +import pykwasm.kwasm_ast as wast + +from .integers import u32 +from .utils import WasmParseError, read_byte + +if TYPE_CHECKING: + from pyk.kast.inner import KInner + + from .utils import InputStream + + +def typeidx(s: InputStream) -> int: + return u32(s) + + +def funcidx(s: InputStream) -> int: + return u32(s) + + +def tableidx(s: InputStream) -> int: + return u32(s) + + +def memidx(s: InputStream) -> int: + return u32(s) + + +def globalidx(s: InputStream) -> int: + return u32(s) + + +def tagidx(s: InputStream) -> int: + return u32(s) + + +def elemidx(s: InputStream) -> int: + return u32(s) + + +def dataidx(s: InputStream) -> int: + return u32(s) + + +def localidx(s: InputStream) -> int: + return u32(s) + + +def labelidx(s: InputStream) -> int: + return u32(s) + + +def externidx(s: InputStream) -> KInner: + match read_byte(s): + case 0x00: + return wast.externidx_func(funcidx(s)) + case 0x01: + return wast.externidx_table(tableidx(s)) + case 0x02: + return wast.externidx_memory(memidx(s)) + case 0x03: + return wast.externidx_global(globalidx(s)) + case 0x04: + return wast.externidx_tag(tagidx(s)) + case x: + raise WasmParseError(f'Invalid externidx descriptor: {x}') diff --git a/pykwasm/src/pykwasm/binary/instructions.py b/pykwasm/src/pykwasm/binary/instructions.py new file mode 100644 index 000000000..6d95ad272 --- /dev/null +++ b/pykwasm/src/pykwasm/binary/instructions.py @@ -0,0 +1,541 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +import pykwasm.kwasm_ast as wast +from pykwasm.binary.combinators import list_of +from pykwasm.binary.floats import f32, f64 +from pykwasm.binary.indices import elemidx, funcidx, globalidx, labelidx, localidx, memidx, tableidx, typeidx +from pykwasm.binary.integers import i32, i64, u32, u64 +from pykwasm.binary.types import heaptype, valtype +from pykwasm.binary.utils import WasmParseError, expect_bytes, peek_byte, read_byte, skip + +if TYPE_CHECKING: + from pyk.kast.inner import KInner + + from pykwasm.binary.utils import InputStream + + +def instr_seq(terminator: int, s: InputStream) -> list[KInner]: + res: list[KInner] = [] + + while peek_byte(s) != terminator: + i = instr(s) + res.append(i) + + expect_bytes(bytes([terminator]), s) + return res + + +def instr(s: InputStream) -> KInner: + def blocktype(s: InputStream) -> KInner: + if peek_byte(s) == 0x40: + skip(1, s) + return wast.vec_type(wast.val_types([])) + + try: + t = valtype(s) + return wast.vec_type(wast.val_types([t])) + except WasmParseError as e: + # TODO handle the `blocktype ::= i:i33` case + raise WasmParseError('Could not parse blocktype') from e + + # returns memory index, alignment, offset + def memarg(s: InputStream) -> tuple[int, int, int]: + n = u32(s) + if n < 2**6: + m = u64(s) + return 0, n, m + elif n < 2**7: + x = memidx(s) + m = u64(s) + return x, n - 2**6, m + else: + raise WasmParseError(f'Invalid memarg. n: {n}') + + opcode = read_byte(s) + # '0xFC' is a special opcode prefix shared by several instructions. + # These instructions are distinguished by a u32 value following the opcode. + additional_value: int | None = None + if opcode == 0xFC: + try: + additional_value = u32(s) + except Exception as e: + raise WasmParseError('Cannot parse opcode, expected u32 after 0xFC') from e + + match opcode: + # Parametric instructions + case 0x00: + return wast.UNREACHABLE + case 0x01: + return wast.NOP + case 0x1A: + return wast.DROP + case 0x1B: + return wast.SELECT + case 0x1C: + # TODO handle ts + ts = list_of(valtype, s) # noqa F841 + return wast.SELECT + + # Control instructions + case 0x02: + bt = blocktype(s) + ins = instr_seq(0x0B, s) + return wast.BLOCK(bt, wast.instrs(ins), wast.KInt(0)) # TODO either track block ids or deprecate them + case 0x03: + bt = blocktype(s) + ins = instr_seq(0x0B, s) + return wast.LOOP(bt, wast.instrs(ins), wast.KInt(0)) # TODO either track block ids or deprecate them + case 0x04: + bt = blocktype(s) + in1 = instr_seq(0x05, s) + in2 = instr_seq(0x0B, s) + return wast.IF( + bt, wast.instrs(in1), wast.instrs(in2), wast.KInt(0) + ) # TODO either track block ids or deprecate them + case 0x0C: + l = labelidx(s) + return wast.BR(l) + case 0x0D: + l = labelidx(s) + return wast.BR_IF(l) + case 0x0E: + ls = list_of(labelidx, s) + ln = labelidx(s) + return wast.BR_TABLE(tuple(ls), ln) + case 0x0F: + return wast.RETURN + case 0x10: + x = funcidx(s) + return wast.CALL(x) + case 0x11: + y = typeidx(s) + x = tableidx(s) + return wast.CALL_INDIRECT(x, y) + + # Variable instructions + case 0x20: + x = localidx(s) + return wast.GET_LOCAL(x) + case 0x21: + x = localidx(s) + return wast.SET_LOCAL(x) + case 0x22: + x = localidx(s) + return wast.TEE_LOCAL(x) + case 0x23: + x = globalidx(s) + return wast.GET_GLOBAL(x) + case 0x24: + x = globalidx(s) + return wast.SET_GLOBAL(x) + + # Table instructions + case 0x25: + x = tableidx(s) + return wast.TABLE_GET(x) + case 0x26: + x = tableidx(s) + return wast.TABLE_SET(x) + case 0xFC if additional_value == 12: + y = elemidx(s) + x = tableidx(s) + return wast.TABLE_INIT(x, y) + case 0xFC if additional_value == 13: + x = elemidx(s) + return wast.ELEM_DROP(x) + case 0xFC if additional_value == 14: + x1 = tableidx(s) + x2 = tableidx(s) + return wast.TABLE_COPY(x1, x2) + case 0xFC if additional_value == 15: + x = tableidx(s) + return wast.TABLE_GROW(x) + case 0xFC if additional_value == 16: + x = tableidx(s) + return wast.TABLE_SIZE(x) + case 0xFC if additional_value == 17: + x = tableidx(s) + return wast.TABLE_FILL(x) + + # Memory instructions + # TODO handle memory index and alignment + case 0x28: + mx, align, offset = memarg(s) # noqa F841 + return wast.I32_LOAD(offset) + case 0x29: + mx, align, offset = memarg(s) # noqa F841 + return wast.I64_LOAD(offset) + case 0x2A: + mx, align, offset = memarg(s) # noqa F841 + return wast.F32_LOAD(offset) + case 0x2B: + mx, align, offset = memarg(s) # noqa F841 + return wast.F64_LOAD(offset) + case 0x2C: + mx, align, offset = memarg(s) # noqa F841 + return wast.I32_LOAD8_S(offset) + case 0x2D: + mx, align, offset = memarg(s) # noqa F841 + return wast.I32_LOAD8_U(offset) + case 0x2E: + mx, align, offset = memarg(s) # noqa F841 + return wast.I32_LOAD16_S(offset) + case 0x2F: + mx, align, offset = memarg(s) # noqa F841 + return wast.I32_LOAD16_U(offset) + case 0x30: + mx, align, offset = memarg(s) # noqa F841 + return wast.I64_LOAD8_S(offset) + case 0x31: + mx, align, offset = memarg(s) # noqa F841 + return wast.I64_LOAD8_U(offset) + case 0x32: + mx, align, offset = memarg(s) # noqa F841 + return wast.I64_LOAD16_S(offset) + case 0x33: + mx, align, offset = memarg(s) # noqa F841 + return wast.I64_LOAD16_U(offset) + case 0x34: + mx, align, offset = memarg(s) # noqa F841 + return wast.I64_LOAD32_S(offset) + case 0x35: + mx, align, offset = memarg(s) # noqa F841 + return wast.I64_LOAD32_U(offset) + case 0x36: + mx, align, offset = memarg(s) # noqa F841 + return wast.I32_STORE(offset) + case 0x37: + mx, align, offset = memarg(s) # noqa F841 + return wast.I64_STORE(offset) + case 0x38: + mx, align, offset = memarg(s) # noqa F841 + return wast.F32_STORE(offset) + case 0x39: + mx, align, offset = memarg(s) # noqa F841 + return wast.F64_STORE(offset) + case 0x3A: + mx, align, offset = memarg(s) # noqa F841 + return wast.I32_STORE8(offset) + case 0x3B: + mx, align, offset = memarg(s) # noqa F841 + return wast.I32_STORE16(offset) + case 0x3C: + mx, align, offset = memarg(s) # noqa F841 + return wast.I64_STORE8(offset) + case 0x3D: + mx, align, offset = memarg(s) # noqa F841 + return wast.I64_STORE16(offset) + case 0x3E: + mx, align, offset = memarg(s) # noqa F841 + return wast.I64_STORE32(offset) + case 0x3F: + # TODO use memory index + _x = memidx(s) # noqa 841 + return wast.MEMORY_SIZE + case 0x40: + # TODO use memory index + _x = memidx(s) # noqa 841 + return wast.MEMORY_GROW + + # Reference instructions + case 0xD0: + ht = heaptype(s) + return wast.REF_NULL(ht) + case 0xD1: + return wast.REF_IS_NULL + case 0xD2: + x = funcidx(s) + return wast.REF_FUNC(x) + + # Numeric instructions + # Constants + case 0x41: + i = i32(s) + return wast.I32_CONST(i) + case 0x42: + i = i64(s) + return wast.I64_CONST(i) + case 0x43: + f = f32(s) + return wast.F32_CONST(f) + case 0x44: + f = f64(s) + return wast.F64_CONST(f) + + # i32 comparison + case 0x45: + return wast.I32_EQZ + case 0x46: + return wast.I32_EQ + case 0x47: + return wast.I32_NE + case 0x48: + return wast.I32_LT_S + case 0x49: + return wast.I32_LT_U + case 0x4A: + return wast.I32_GT_S + case 0x4B: + return wast.I32_GT_U + case 0x4C: + return wast.I32_LE_S + case 0x4D: + return wast.I32_LE_U + case 0x4E: + return wast.I32_GE_S + case 0x4F: + return wast.I32_GE_U + + # i64 comparison + case 0x50: + return wast.I64_EQZ + case 0x51: + return wast.I64_EQ + case 0x52: + return wast.I64_NE + case 0x53: + return wast.I64_LT_S + case 0x54: + return wast.I64_LT_U + case 0x55: + return wast.I64_GT_S + case 0x56: + return wast.I64_GT_U + case 0x57: + return wast.I64_LE_S + case 0x58: + return wast.I64_LE_U + case 0x59: + return wast.I64_GE_S + case 0x5A: + return wast.I64_GE_U + + # f32 comparison + case 0x5B: + return wast.F32_EQ + case 0x5C: + return wast.F32_NE + case 0x5D: + return wast.F32_LT + case 0x5E: + return wast.F32_GT + case 0x5F: + return wast.F32_LE + case 0x60: + return wast.F32_GE + + # f64 comparison + case 0x61: + return wast.F64_EQ + case 0x62: + return wast.F64_NE + case 0x63: + return wast.F64_LT + case 0x64: + return wast.F64_GT + case 0x65: + return wast.F64_LE + case 0x66: + return wast.F64_GE + + # i32 unary / binary + case 0x67: + return wast.I32_CLZ + case 0x68: + return wast.I32_CTZ + case 0x69: + return wast.I32_POPCNT + case 0x6A: + return wast.I32_ADD + case 0x6B: + return wast.I32_SUB + case 0x6C: + return wast.I32_MUL + case 0x6D: + return wast.I32_DIV_S + case 0x6E: + return wast.I32_DIV_U + case 0x6F: + return wast.I32_REM_S + case 0x70: + return wast.I32_REM_U + case 0x71: + return wast.I32_AND + case 0x72: + return wast.I32_OR + case 0x73: + return wast.I32_XOR + case 0x74: + return wast.I32_SHL + case 0x75: + return wast.I32_SHR_S + case 0x76: + return wast.I32_SHR_U + case 0x77: + return wast.I32_ROTL + case 0x78: + return wast.I32_ROTR + + # i64 unary / binary + case 0x79: + return wast.I64_CLZ + case 0x7A: + return wast.I64_CTZ + case 0x7B: + return wast.I64_POPCNT + case 0x7C: + return wast.I64_ADD + case 0x7D: + return wast.I64_SUB + case 0x7E: + return wast.I64_MUL + case 0x7F: + return wast.I64_DIV_S + case 0x80: + return wast.I64_DIV_U + case 0x81: + return wast.I64_REM_S + case 0x82: + return wast.I64_REM_U + case 0x83: + return wast.I64_AND + case 0x84: + return wast.I64_OR + case 0x85: + return wast.I64_XOR + case 0x86: + return wast.I64_SHL + case 0x87: + return wast.I64_SHR_S + case 0x88: + return wast.I64_SHR_U + case 0x89: + return wast.I64_ROTL + case 0x8A: + return wast.I64_ROTR + + # f32 unary / binary + case 0x8B: + return wast.F32_ABS + case 0x8C: + return wast.F32_NEG + case 0x8D: + return wast.F32_CEIL + case 0x8E: + return wast.F32_FLOOR + case 0x8F: + return wast.F32_TRUNC + case 0x90: + return wast.F32_NEAREST + case 0x91: + return wast.F32_SQRT + case 0x92: + return wast.F32_ADD + case 0x93: + return wast.F32_SUB + case 0x94: + return wast.F32_MUL + case 0x95: + return wast.F32_DIV + case 0x96: + return wast.F32_MIN + case 0x97: + return wast.F32_MAX + case 0x98: + return wast.F32_COPYSIGN + + # f64 unary / binary + case 0x99: + return wast.F64_ABS + case 0x9A: + return wast.F64_NEG + case 0x9B: + return wast.F64_CEIL + case 0x9C: + return wast.F64_FLOOR + case 0x9D: + return wast.F64_TRUNC + case 0x9E: + return wast.F64_NEAREST + case 0x9F: + return wast.F64_SQRT + case 0xA0: + return wast.F64_ADD + case 0xA1: + return wast.F64_SUB + case 0xA2: + return wast.F64_MUL + case 0xA3: + return wast.F64_DIV + case 0xA4: + return wast.F64_MIN + case 0xA5: + return wast.F64_MAX + case 0xA6: + return wast.F64_COPYSIGN + + # conversion instructions + case 0xA7: + return wast.I32_WRAP_I64 + case 0xA8: + return wast.I32_TRUNC_S_F32 + case 0xA9: + return wast.I32_TRUNC_U_F32 + case 0xAA: + return wast.I32_TRUNC_S_F64 + case 0xAB: + return wast.I32_TRUNC_U_F64 + case 0xAC: + return wast.I64_EXTEND_S_I32 + case 0xAD: + return wast.I64_EXTEND_U_I32 + case 0xAE: + return wast.I64_TRUNC_S_F32 + case 0xAF: + return wast.I64_TRUNC_U_F32 + case 0xB0: + return wast.I64_TRUNC_S_F64 + case 0xB1: + return wast.I64_TRUNC_U_F64 + case 0xB2: + return wast.F32_CONVERT_S_I32 + case 0xB3: + return wast.F32_CONVERT_U_I32 + case 0xB4: + return wast.F32_CONVERT_S_I64 + case 0xB5: + return wast.F32_CONVERT_U_I64 + case 0xB6: + return wast.F32_DEMOTE_F64 + case 0xB7: + return wast.F64_CONVERT_S_I32 + case 0xB8: + return wast.F64_CONVERT_U_I32 + case 0xB9: + return wast.F64_CONVERT_S_I64 + case 0xBA: + return wast.F64_CONVERT_U_I64 + case 0xBB: + return wast.F64_PROMOTE_F32 + + # sign-extension instructions + case 0xC0: + return wast.I32_EXTEND8_s + case 0xC1: + return wast.I32_EXTEND16_s + case 0xC2: + return wast.I64_EXTEND8_s + case 0xC3: + return wast.I64_EXTEND16_s + case 0xC4: + return wast.I64_EXTEND32_s + + # Handle unsupported opcodes + case _: + raise WasmParseError(f'Unsupported opcode: {opcode :#04x}, {additional_value}') + + +def expr(s: InputStream) -> list[KInner]: + return instr_seq(0x0B, s) diff --git a/pykwasm/src/pykwasm/binary/module.py b/pykwasm/src/pykwasm/binary/module.py new file mode 100644 index 000000000..38b991078 --- /dev/null +++ b/pykwasm/src/pykwasm/binary/module.py @@ -0,0 +1,345 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pyk.kast.inner import KApply, KLabel, KSort, KToken + +import pykwasm.kwasm_ast as wast +from pykwasm.binary.indices import externidx, funcidx, memidx, tableidx, typeidx +from pykwasm.binary.instructions import expr + +from .combinators import list_of, sized +from .integers import u32 +from .types import externtype_as_import_desc, globaltype, memtype, rectype, reftype, tabletype, valtype +from .utils import WasmEOFError, WasmParseError, peek_byte, peek_bytes, read_byte, read_bytes, skip + +if TYPE_CHECKING: + from pyk.kast.inner import KInner + + from .types import RecType + from .utils import A, InputStream, Parser + +MAGIC = b'\x00\x61\x73\x6d' +VERSION = b'\x01\x00\x00\x00' + +EMPTY_SECTION: KInner = wast.EMPTY_DEFNS + + +def parse_magic(s: InputStream) -> None: + try: + val = s.read(4) + assert val == MAGIC + except Exception as e: + raise WasmParseError('Could not read magic value') from e + + +def parse_version(s: InputStream) -> None: + try: + val = s.read(4) + assert val == VERSION + except Exception as e: + raise WasmParseError('Could not read version') from e + + +def parse_custom_section(s: InputStream) -> bytes: + try: + n = peek_byte(s) + except WasmEOFError: + return b'' + + if n != 0: + return b'' + skip(1, s) + return read_bytes(n, s) + + +def parse_custom_sections(s: InputStream) -> list[bytes]: + sects = [] + while True: + sect = parse_custom_section(s) + if not sect: + break + sects.append(sect) + return sects + + +def section(sec_id: int, p: Parser[A], default: A, s: InputStream) -> A: + try: + n = peek_byte(s) + except WasmEOFError: + return default + + if n != sec_id: + return default + skip(1, s) + return sized(p, s) + + +# TODO support recursive types in type sections +def typesec(s: InputStream) -> KInner: + def rectype_to_type_decl(rc: RecType) -> KInner: + match rc: + case [(_, [], st)]: + return wast.type(st) + case _: + raise WasmParseError('recursive types are not supported') + + rectypes = list_of(rectype, s) + type_decls = [rectype_to_type_decl(t) for t in rectypes] + return wast.defns(type_decls) + + +def byte_list(s: InputStream) -> bytes: + n = u32(s) + return read_bytes(n, s) + + +def name(s: InputStream) -> str: + bs = byte_list(s) + return bs.decode('utf-8') + + +def importsec(s: InputStream) -> KInner: + def import_(s: InputStream) -> KInner: + nm1 = wast.wasm_string(name(s)) + nm2 = wast.wasm_string(name(s)) + desc = externtype_as_import_desc(s) + return wast.imp(nm1, nm2, desc) + + imports = list_of(import_, s) + return wast.defns(imports) + + +def funcsec(s: InputStream) -> list[int]: + return list_of(typeidx, s) + + +def tablesec(s: InputStream) -> KInner: + def table(s: InputStream) -> KInner: + match peek_bytes(2, s): + case b'\x40\x00': + raise WasmParseError('table_tt_with_initializer not implemented') + case _: + # TODO this should return '𝗍𝖺𝖻𝗅𝖾 tt (𝗋𝖾𝖿.π—‡π—Žπ—…π—… ht) if tt=at lim (𝗋𝖾𝖿 π—‡π—Žπ—…π—…? ht)' + _at, lim, rt = tabletype(s) + return wast.table(lim, rt) + + tables = list_of(table, s) + return wast.defns(tables) + + +def memsec(s: InputStream) -> KInner: + mems = list_of(memtype, s) + return wast.defns([wast.memory(lim) for _at, lim in mems]) + + +def globalsec(s: InputStream) -> KInner: + def glob(s: InputStream) -> KInner: + gt = globaltype(s) + e = wast.instrs(expr(s)) + return wast.glob(gt, e) + + return wast.defns(list_of(glob, s)) + + +def exportsec(s: InputStream) -> KInner: + def export(s: InputStream) -> KInner: + nm = name(s) + xx = externidx(s) + return wast.export(wast.wasm_string(nm), index=xx) + + return wast.defns(list_of(export, s)) + + +def startsec(s: InputStream) -> KInner: + start_func = funcidx(s) + return wast.defns([wast.start(start_func)]) + + +def elemsec(s: InputStream) -> KInner: + def elemkind(s: InputStream) -> KInner: + match read_byte(s): + case 0: + return wast.funcref + case x: + raise WasmParseError(f'Invalid elemkind: {x}') + + def elem_init(init: list[list[KInner]]) -> list[int | None]: + def expr_to_int(expr: list[KInner]) -> int | None: + # 'expr' must be a constant expression consisting of a reference instruction + match expr: + case [KApply(KLabel('aRef.func'), (KToken(sfuncidx, KSort('Int')),))]: + return int(sfuncidx) + case [KApply(KLabel('aRef.null'), _)]: + return None + case _: + raise WasmParseError(f'Expected a constant reference instruction as elem initializer, got {expr}') + + return [expr_to_int(e) for e in init] + + def elem(s: InputStream) -> KInner: + match read_byte(s): + case 0: + e0 = expr(s) + ys = list_of(funcidx, s) + return wast.elem(wast.funcref, wast.elem_active(0, wast.instrs(e0)), ys) + case 1: + rt = elemkind(s) + ys = list_of(funcidx, s) + return wast.elem(rt, wast.elem_passive(), ys) + case 2: + x = tableidx(s) + e = expr(s) + rt = elemkind(s) + ys = list_of(funcidx, s) + return wast.elem(rt, wast.elem_active(x, wast.instrs(e)), ys) + case 3: + rt = elemkind(s) + ys = list_of(funcidx, s) + return wast.elem(rt, wast.elem_declarative(), ys) + case 4: + e0 = expr(s) + es = list_of(expr, s) + return wast.elem(wast.funcref, wast.elem_active(0, wast.instrs(e0)), elem_init(es)) + case 5: + rt = reftype(s) + es = list_of(expr, s) + return wast.elem(rt, wast.elem_passive(), elem_init(es)) + case 6: + x = tableidx(s) + e0 = expr(s) + rt = reftype(s) + es = list_of(expr, s) + return wast.elem(rt, wast.elem_active(x, wast.instrs(e0)), elem_init(es)) + case 7: + rt = reftype(s) + es = list_of(expr, s) + return wast.elem(rt, wast.elem_declarative(), elem_init(es)) + case x: + raise WasmParseError(f'Invalid elem descriptor: {x}') + + return wast.defns(list_of(elem, s)) + + +def codesec(s: InputStream) -> list[tuple[list[KInner], list[KInner]]]: + def locals(s: InputStream) -> tuple[int, KInner]: + n = u32(s) + t = valtype(s) + return n, t + + def func(s: InputStream) -> tuple[list[KInner], list[KInner]]: + locs = list_of(locals, s) + body = expr(s) + + locs_flattened = [t for n, t in locs for _ in range(n)] + return locs_flattened, body + + def code(s: InputStream) -> tuple[list[KInner], list[KInner]]: + return sized(func, s) + + return list_of(code, s) + + +def datasec(s: InputStream) -> KInner: + def data(s: InputStream) -> KInner: + match read_byte(s): + case 0: + e = expr(s) + bs = byte_list(s) + return wast.data(bs, wast.datamode_active(0, wast.instrs(e))) + case 1: + bs = byte_list(s) + return wast.data(bs, wast.datamode_passive()) + case 2: + x = memidx(s) + e = expr(s) + bs = byte_list(s) + return wast.data(bs, wast.datamode_active(x, wast.instrs(e))) + case x: + raise WasmParseError(f'Invalid data segment descriptor: {x}') + + return wast.defns(list_of(data, s)) + + +def datacntsec(s: InputStream) -> int | None: + return u32(s) + + +def parse_module(s: InputStream) -> KInner: + parse_magic(s) + parse_version(s) + + custom_sections = [] + customs = lambda: custom_sections.extend(parse_custom_sections(s)) + + customs() + + type_section = section(1, typesec, EMPTY_SECTION, s) + + customs() + + import_section = section(2, importsec, EMPTY_SECTION, s) + + customs() + + function_section: list[int] = section(3, funcsec, [], s) + + customs() + + table_section = section(4, tablesec, EMPTY_SECTION, s) + + customs() + + memory_section = section(5, memsec, EMPTY_SECTION, s) + + customs() + + global_section = section(6, globalsec, EMPTY_SECTION, s) + + customs() + + export_section = section(7, exportsec, EMPTY_SECTION, s) + + customs() + + start_section = section(8, startsec, EMPTY_SECTION, s) + + customs() + + elem_section = section(9, elemsec, EMPTY_SECTION, s) + + customs() + + data_cnt_section = section(12, datacntsec, None, s) # noqa f841 + + customs() + + code_section: list[tuple[list[KInner], list[KInner]]] = section(10, codesec, [], s) + + customs() + + data_section = section(11, datasec, EMPTY_SECTION, s) + + customs() + + functions = [ + wast.func( + wast.KInt(typ_idx), + wast.vec_type(wast.val_types(locals)), + wast.instrs(code), + ) + for typ_idx, (locals, code) in zip(function_section, code_section, strict=True) + ] + + return wast.module( + types=type_section, + funcs=wast.defns(functions), + tables=table_section, + mems=memory_section, + globs=global_section, + elem=elem_section, + data=data_section, + start=start_section, + imports=import_section, + exports=export_section, + ) diff --git a/pykwasm/src/pykwasm/binary/types.py b/pykwasm/src/pykwasm/binary/types.py new file mode 100644 index 000000000..b5cecae18 --- /dev/null +++ b/pykwasm/src/pykwasm/binary/types.py @@ -0,0 +1,204 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pykwasm.binary.indices import typeidx + +from .. import kwasm_ast as wast +from .combinators import either, list_of +from .integers import u64 +from .utils import WasmParseError, peek_byte, read_byte, skip + +if TYPE_CHECKING: + from typing import TypeAlias + + from pyk.kast.inner import KInner + + from .utils import InputStream + +# subtype struct in recursive types +# subtype ::= sub final? typeuse* comptype +SubType: TypeAlias = tuple[bool, list[int], 'KInner'] + +# A group of mutually recursive composite types +# rectype :: = rec list(subtype) +RecType: TypeAlias = list[SubType] + + +def rectype(s: InputStream) -> RecType: + n = peek_byte(s) + if n == 0x4E: + skip(1, s) + sts = list_of(parse_subtype, s) + return sts + return [parse_subtype(s)] + + +def parse_subtype(s: InputStream) -> SubType: + n = peek_byte(s) + + if n == 0x4F: + skip(1, s) + xs = list_of(typeidx, s) + ct = parse_comptype(s) + return (True, xs, ct) + if n == 0x50: + skip(1, s) + xs = list_of(typeidx, s) + ct = parse_comptype(s) + return (False, xs, ct) + + ct = parse_comptype(s) + return (True, [], ct) + + +def parse_comptype(s: InputStream) -> KInner: + n = read_byte(s) + if n == 0x60: + ts1 = resulttype(s) + ts2 = resulttype(s) + return wast.func_type(wast.vec_type(wast.val_types(ts1)), wast.vec_type(wast.val_types(ts2))) + + if n == 0x5E: + raise WasmParseError('array composite type is not yet supported') + if n == 0x5F: + raise WasmParseError('struct composite type is not yet supported') + raise WasmParseError(f'composite type not yet supported: {n}') + + +def resulttype(s: InputStream) -> list[KInner]: + return list_of(valtype, s) + + +def valtype(s: InputStream) -> KInner: + d = peek_byte(s) + + try: + return either( + [ + numtype, + reftype, + # TODO implement vectypes + # vectype, + ], + s, + ) + except WasmParseError: + raise WasmParseError(f'Could not parse valtype. Descriptor: {d}') from None + + +def reftype(s: InputStream) -> KInner: + match read_byte(s): + case 0x70: + return wast.funcref + case 0x6F: + return wast.externref + case d: + raise WasmParseError(f'Unsupported reftype descriptor: {d}') + + +def numtype(s: InputStream) -> KInner: + match read_byte(s): + case 0x7C: + return wast.f64 + case 0x7D: + return wast.f32 + case 0x7E: + return wast.i64 + case 0x7F: + return wast.i32 + case d: + raise WasmParseError(f'Invalid numtype descriptor: {d}') + + +Limits: TypeAlias = tuple[int, int | None] + + +# Returns (addressType, (n, m)) +def limits(s: InputStream) -> tuple[KInner, Limits]: + match read_byte(s): + case 0x00: + n = u64(s) + return wast.i32, (n, None) + case 0x01: + n = u64(s) + m = u64(s) + return wast.i32, (n, m) + case 0x02: + n = u64(s) + return wast.i64, (n, None) + case 0x03: + n = u64(s) + m = u64(s) + return wast.i64, (n, m) + case d: + raise WasmParseError(f'Invalid limit descriptor: {d}') + + +# Returns (addressType, limits, reftype) +def tabletype(s: InputStream) -> tuple[KInner, Limits, KInner]: + rt = reftype(s) + at, lim = limits(s) + return at, lim, rt + + +# Returns (addressType, limits) +def memtype(s: InputStream) -> tuple[KInner, Limits]: + return limits(s) + + +def mut(s: InputStream) -> KInner: + match read_byte(s): + case 0x00: + return wast.MUT_CONST + case 0x01: + return wast.MUT_VAR + case d: + raise WasmParseError(f'Invalid mut descriptor. Expected 0x00 or 0x01, got: {d}') + + +def globaltype(s: InputStream) -> KInner: + t = valtype(s) + m = mut(s) + return wast.global_type(m, t) + + +def tagtype(s: InputStream) -> int: + match read_byte(s): + case 0x00: + return typeidx(s) + case d: + raise WasmParseError(f'Invalid tagtype descriptor. Expected 0x00, got: {d}') + + +# TODO this function parses externtype from wasm 3.0 but returns ImportDesc from Wasm 1.0. +# Update the ImportDefn K definition to reflect the changes. +def externtype_as_import_desc(s: InputStream) -> KInner: + match read_byte(s): + case 0x00: + x = typeidx(s) + return wast.func_desc(x) + case 0x01: + _at, lim, _rt = tabletype(s) + return wast.table_desc(lim) + case 0x02: + _at, lim = memtype(s) + return wast.memory_desc(lim) + case 0x03: + gt = globaltype(s) + return wast.global_desc(gt) + case 0x04: + # jt = tagtype(s) + raise WasmParseError('externtype tagtype variant is not yet supported.') + case d: + raise WasmParseError(f'Invalid externtype descriptor. Expected [0x00-0x04], got: {d}') + + +def heaptype(s: InputStream) -> KInner: + match read_byte(s): + case 0x6F: + return wast.HEAPTYPE_EXTERN + case 0x70: + return wast.HEAPTYPE_FUNC + case x: + raise WasmParseError(f'Unsupported heaptype descriptor: {x}') From 56e7be26a97a1365357702d35d12800233e59877 Mon Sep 17 00:00:00 2001 From: Burak Bilge Yalcinkaya Date: Fri, 3 Apr 2026 14:12:33 +0300 Subject: [PATCH 04/15] add integrations tests and compare legacy and new parser --- .../tests/integration/test_binary_parser.py | 69 +++++++++++++++++-- 1 file changed, 65 insertions(+), 4 deletions(-) diff --git a/pykwasm/src/tests/integration/test_binary_parser.py b/pykwasm/src/tests/integration/test_binary_parser.py index d0c76404f..20dd00a2e 100644 --- a/pykwasm/src/tests/integration/test_binary_parser.py +++ b/pykwasm/src/tests/integration/test_binary_parser.py @@ -7,18 +7,21 @@ from typing import TYPE_CHECKING import pytest -from pyk.kast.inner import KSequence, KSort, Subst +from pyk.kast.inner import KApply, KLabel, KSequence, KSort, Subst from pyk.kast.manip import split_config_from +from pyk.kast.prelude.utils import token +from pykwasm.binary.module import parse_module from pykwasm.wasm2kast import wasm2kast if TYPE_CHECKING: from pyk.kast import KInner + from pyk.kore.syntax import Pattern from pyk.ktool.krun import KRun BINARY_DIR = Path(__file__).parent / 'binary' -BINARY_WAT_FILES = BINARY_DIR.glob('*.wat') +BINARY_WAT_FILES = list(BINARY_DIR.glob('*.wat')) sys.setrecursionlimit(1500000000) @@ -39,7 +42,49 @@ def test_wasm2kast(krun_llvm: KRun, wat_path: Path) -> None: run_module(krun_llvm, module) -def run_module(krun: KRun, parsed_module: KInner) -> None: +@pytest.mark.parametrize('wat_path', BINARY_WAT_FILES, ids=str) +def test_self_binary_parser(krun_llvm: KRun, wat_path: Path) -> None: + # Given + wat2wasm_cmd = ['wat2wasm', str(wat_path), '--output=/dev/stdout'] + proc_res = run(wat2wasm_cmd, check=True, capture_output=True) + wasm_file = BytesIO(proc_res.stdout) + + assert not proc_res.returncode + + # When + module = parse_module(wasm_file) + + # Then + + # Can convert to Kore successfully + krun_llvm.kast_to_kore(module, KSort('ModuleDecl')) + + # Can run (initiate) successfully + run_module(krun_llvm, module) + + +@pytest.mark.parametrize('wat_path', BINARY_WAT_FILES, ids=str) +def test_diff(krun_llvm: KRun, wat_path: Path) -> None: + # Given + wat2wasm_cmd = ['wat2wasm', str(wat_path), '--output=/dev/stdout'] + proc_res = run(wat2wasm_cmd, check=True, capture_output=True) + + assert not proc_res.returncode + + # When + module_wasm2kast = remove_block_ids(wasm2kast(BytesIO(proc_res.stdout))) + + module_self = parse_module(BytesIO(proc_res.stdout)) + + # Then + assert module_wasm2kast == module_self + + run_wasm2kast = run_module(krun_llvm, module_wasm2kast) + run_self = run_module(krun_llvm, module_self) + assert run_wasm2kast == run_self + + +def run_module(krun: KRun, parsed_module: KInner) -> Pattern: try: # Create an initial config config_kast = krun.definition.init_config(KSort('GeneratedTopCell')) @@ -53,7 +98,23 @@ def run_module(krun: KRun, parsed_module: KInner) -> None: config_kore = krun.kast_to_kore(config_with_module, KSort('GeneratedTopCell')) # Run the config - krun.run_pattern(config_kore) + return krun.run_pattern(config_kore) except Exception as e: raise Exception('Received error while running') from e + + +def remove_block_ids(k: KInner) -> KInner: + match k: + + case KApply(KLabel('aBlock'), (vec_type, instrs, _)): + return KApply('aBlock', (vec_type, remove_block_ids(instrs), token(0))) + + case KApply(KLabel('aIf'), (vec_type, then_instrs, else_instrs, _)): + return KApply('aIf', (vec_type, remove_block_ids(then_instrs), remove_block_ids(else_instrs), token(0))) + + case KApply(KLabel('aLoop'), (vec_type, instrs, _)): + return KApply(KLabel('aLoop'), (vec_type, remove_block_ids(instrs), token(0))) + + case _: + return k.map_inner(remove_block_ids) From 4249fc42875521c96ec3aaf22be0b4f6638ab5aa Mon Sep 17 00:00:00 2001 From: devops Date: Fri, 3 Apr 2026 20:35:22 +0000 Subject: [PATCH 05/15] Set Version: 0.1.149 --- package/version | 2 +- pykwasm/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package/version b/package/version index 22a9c16f5..010fbfb88 100644 --- a/package/version +++ b/package/version @@ -1 +1 @@ -0.1.146 +0.1.149 diff --git a/pykwasm/pyproject.toml b/pykwasm/pyproject.toml index a5086deed..f4ae91554 100644 --- a/pykwasm/pyproject.toml +++ b/pykwasm/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "pykwasm" -version = "0.1.146" +version = "0.1.149" description = "" readme = "README.md" requires-python = "~=3.10" From 9992ca9faafb07d15b409d5c9e041b4bbb4cbf7d Mon Sep 17 00:00:00 2001 From: Burak Bilge Yalcinkaya Date: Mon, 6 Apr 2026 16:42:58 +0300 Subject: [PATCH 06/15] format --- pykwasm/src/pykwasm/binary/integers.py | 3 +- .../tests/integration/test_binary_parser.py | 8 ++--- pykwasm/src/tests/unit/test_binary_parser.py | 34 ++++++++++--------- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/pykwasm/src/pykwasm/binary/integers.py b/pykwasm/src/pykwasm/binary/integers.py index 0f4479178..e62f95a65 100644 --- a/pykwasm/src/pykwasm/binary/integers.py +++ b/pykwasm/src/pykwasm/binary/integers.py @@ -60,7 +60,8 @@ def u64(s: InputStream) -> int: def to_uninterpreted(bits: int, x: int) -> int: if 0 <= x: return x - return x + 2 ** bits + return x + 2**bits + def parse_iint(bits: int, s: InputStream) -> int: i = parse_sint(bits, s) diff --git a/pykwasm/src/tests/integration/test_binary_parser.py b/pykwasm/src/tests/integration/test_binary_parser.py index 20dd00a2e..d15873516 100644 --- a/pykwasm/src/tests/integration/test_binary_parser.py +++ b/pykwasm/src/tests/integration/test_binary_parser.py @@ -106,15 +106,15 @@ def run_module(krun: KRun, parsed_module: KInner) -> Pattern: def remove_block_ids(k: KInner) -> KInner: match k: - + case KApply(KLabel('aBlock'), (vec_type, instrs, _)): return KApply('aBlock', (vec_type, remove_block_ids(instrs), token(0))) - + case KApply(KLabel('aIf'), (vec_type, then_instrs, else_instrs, _)): return KApply('aIf', (vec_type, remove_block_ids(then_instrs), remove_block_ids(else_instrs), token(0))) - + case KApply(KLabel('aLoop'), (vec_type, instrs, _)): return KApply(KLabel('aLoop'), (vec_type, remove_block_ids(instrs), token(0))) - + case _: return k.map_inner(remove_block_ids) diff --git a/pykwasm/src/tests/unit/test_binary_parser.py b/pykwasm/src/tests/unit/test_binary_parser.py index f1f469356..9199d216b 100644 --- a/pykwasm/src/tests/unit/test_binary_parser.py +++ b/pykwasm/src/tests/unit/test_binary_parser.py @@ -1,9 +1,11 @@ import io import struct + import pytest -from pykwasm.binary.utils import WasmParseError, WasmEOFError from pykwasm.binary import floats, integers +from pykwasm.binary.utils import WasmEOFError + def stream(data: bytes) -> io.BytesIO: """Helper: wrap bytes in a seekable stream.""" @@ -13,21 +15,21 @@ def stream(data: bytes) -> io.BytesIO: class TestFloats: VALUES = [0.0, 3.14, -1.5, 1.23456789, -9.99, float('inf'), float('-inf')] - @pytest.mark.parametrize("value", VALUES) - def test_f32(self, value): + @pytest.mark.parametrize('value', VALUES) + def test_f32(self, value: float) -> None: encoded = struct.pack(' None: encoded = struct.pack(' None: with pytest.raises(WasmEOFError): floats.f32(stream(b'\x00\x01')) # only 2 bytes, needs 4 - def test_f64_eof_raises(self): + def test_f64_eof_raises(self) -> None: with pytest.raises(WasmEOFError): floats.f64(stream(b'\x00\x01\x02\x03\x04')) # only 5 bytes, needs 8 @@ -63,24 +65,24 @@ def encode_sleb128(value: int) -> bytes: buf.append(b | 0x80) return bytes(buf) - @pytest.mark.parametrize("value", U32_VALUES) - def test_unsigned_32(self, value): + @pytest.mark.parametrize('value', U32_VALUES) + def test_unsigned_32(self, value: int) -> None: encoded = self.encode_uleb128(value) assert integers.u32(stream(encoded)) == value - @pytest.mark.parametrize("value", U64_VALUES) - def test_unsigned_64(self, value): + @pytest.mark.parametrize('value', U64_VALUES) + def test_unsigned_64(self, value: int) -> None: encoded = self.encode_uleb128(value) assert integers.u64(stream(encoded)) == value - @pytest.mark.parametrize("value", I32_VALUES) - def test_uninterpreted_32(self, value): + @pytest.mark.parametrize('value', I32_VALUES) + def test_uninterpreted_32(self, value: int) -> None: expected = integers.to_uninterpreted(32, value) encoded = self.encode_sleb128(value) assert integers.i32(stream(encoded)) == expected - @pytest.mark.parametrize("value", I64_VALUES) - def test_uninterpreted_64(self, value): + @pytest.mark.parametrize('value', I64_VALUES) + def test_uninterpreted_64(self, value: int) -> None: expected = integers.to_uninterpreted(64, value) encoded = self.encode_sleb128(value) - assert integers.i64(stream(encoded)) == expected \ No newline at end of file + assert integers.i64(stream(encoded)) == expected From be1806369fe829ba861ff8f63062858837123d84 Mon Sep 17 00:00:00 2001 From: Burak Bilge Yalcinkaya Date: Mon, 6 Apr 2026 16:48:40 +0300 Subject: [PATCH 07/15] pyupgrade --- pykwasm/src/pykwasm/binary/utils.py | 3 ++- pykwasm/uv.lock | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pykwasm/src/pykwasm/binary/utils.py b/pykwasm/src/pykwasm/binary/utils.py index 4efa5d44d..7934848dc 100644 --- a/pykwasm/src/pykwasm/binary/utils.py +++ b/pykwasm/src/pykwasm/binary/utils.py @@ -1,4 +1,5 @@ -from typing import BinaryIO, Callable, TypeAlias, TypeVar +from typing import BinaryIO, TypeAlias, TypeVar +from collections.abc import Callable InputStream: TypeAlias = BinaryIO diff --git a/pykwasm/uv.lock b/pykwasm/uv.lock index ece16d297..e716cf377 100644 --- a/pykwasm/uv.lock +++ b/pykwasm/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 2 requires-python = ">=3.10, <4" resolution-markers = [ "python_full_version >= '3.11'", @@ -432,7 +432,7 @@ name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ @@ -1055,7 +1055,7 @@ wheels = [ [[package]] name = "pykwasm" -version = "0.1.146" +version = "0.1.149" source = { editable = "." } dependencies = [ { name = "kframework" }, From cd839324b95f3a094d4dfb10206f2c4e57f5caa1 Mon Sep 17 00:00:00 2001 From: Burak Bilge Yalcinkaya Date: Mon, 6 Apr 2026 17:14:56 +0300 Subject: [PATCH 08/15] organize imports --- pykwasm/src/pykwasm/binary/utils.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/pykwasm/src/pykwasm/binary/utils.py b/pykwasm/src/pykwasm/binary/utils.py index 7934848dc..232d2dcf0 100644 --- a/pykwasm/src/pykwasm/binary/utils.py +++ b/pykwasm/src/pykwasm/binary/utils.py @@ -1,11 +1,16 @@ -from typing import BinaryIO, TypeAlias, TypeVar -from collections.abc import Callable +from __future__ import annotations -InputStream: TypeAlias = BinaryIO +from typing import TYPE_CHECKING -A = TypeVar('A') -Parser: TypeAlias = Callable[[InputStream], A] -NParser: TypeAlias = Callable[[int, InputStream], A] +if TYPE_CHECKING: + from collections.abc import Callable + from typing import TYPE_CHECKING, BinaryIO, TypeAlias, TypeVar + + InputStream: TypeAlias = BinaryIO + + A = TypeVar('A') + Parser: TypeAlias = Callable[[InputStream], A] + NParser: TypeAlias = Callable[[int, InputStream], A] def read_bytes(n: int, s: InputStream) -> bytes: From 12a946f1ff3f639da295c784213fb93976633960 Mon Sep 17 00:00:00 2001 From: Burak Bilge Yalcinkaya Date: Tue, 7 Apr 2026 11:16:37 +0300 Subject: [PATCH 09/15] fix ``: migrate to typed export indices --- pykwasm/src/pykwasm/kdist/wasm-semantics/test.md | 4 ++-- pykwasm/src/pykwasm/kdist/wasm-semantics/wasm.md | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pykwasm/src/pykwasm/kdist/wasm-semantics/test.md b/pykwasm/src/pykwasm/kdist/wasm-semantics/test.md index 26f726dc4..f264266ae 100644 --- a/pykwasm/src/pykwasm/kdist/wasm-semantics/test.md +++ b/pykwasm/src/pykwasm/kdist/wasm-semantics/test.md @@ -217,7 +217,7 @@ We allow 2 kinds of actions: rule invoke MODIDX:Int ENAME:WasmString => ( invoke FUNCADDRS {{ IDX }} orDefault -1 ):Instr ... MODIDX - ... ENAME |-> IDX ... + ... ENAME |-> #externIdxFunc( IDX ) ... FUNCADDRS ... @@ -232,7 +232,7 @@ We allow 2 kinds of actions: rule get MODIDX:Int NAME:WasmString => VAL ... MODIDX - ... NAME |-> TFIDX ... + ... NAME |-> #externIdxGlobal( TFIDX ) ... IDS ... #ContextLookup(IDS, TFIDX) |-> ADDR ... ... diff --git a/pykwasm/src/pykwasm/kdist/wasm-semantics/wasm.md b/pykwasm/src/pykwasm/kdist/wasm-semantics/wasm.md index b6270c398..62b5afc0b 100644 --- a/pykwasm/src/pykwasm/kdist/wasm-semantics/wasm.md +++ b/pykwasm/src/pykwasm/kdist/wasm-semantics/wasm.md @@ -213,7 +213,7 @@ module WASM 0 - .Map + .Map // WasmString -> ExternIdx .Map 0 .ListInt @@ -1872,7 +1872,7 @@ The value of a global gets copied when it is imported. MODIDX FS2 - ... NAME |-> IDX ... + ... NAME |-> #externIdxFunc( IDX ) ... ... requires isListIndex(IDX, FS2) @@ -1911,7 +1911,7 @@ The value of a global gets copied when it is imported. MODIDX IDS' ... #ContextLookup(IDS' , TFIDX) |-> ADDR ... - ... NAME |-> TFIDX ... + ... NAME |-> #externIdxTable( TFIDX ) ... ... @@ -1935,7 +1935,7 @@ The value of a global gets copied when it is imported. MODIDX IDS' ... #ContextLookup(IDS' , TFIDX) |-> ADDR ... - ... NAME |-> TFIDX ... + ... NAME |-> #externIdxMemory( TFIDX ) ... ... @@ -1960,7 +1960,7 @@ The value of a global gets copied when it is imported. MODIDX IDS' ... #ContextLookup(IDS' , TFIDX) |-> ADDR ... - ... NAME |-> TFIDX ... + ... NAME |-> #externIdxGlobal( TFIDX ) ... ... From d16b9cc29fe840be3c0fa489638461576bd8b8ea Mon Sep 17 00:00:00 2001 From: devops Date: Thu, 21 May 2026 18:25:01 +0000 Subject: [PATCH 10/15] Set Version: 0.1.155 --- package/version | 2 +- pykwasm/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package/version b/package/version index 3671a92a3..7a29ae6cd 100644 --- a/package/version +++ b/package/version @@ -1 +1 @@ -0.1.154 +0.1.155 diff --git a/pykwasm/pyproject.toml b/pykwasm/pyproject.toml index 7684dc8a1..93762a796 100644 --- a/pykwasm/pyproject.toml +++ b/pykwasm/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "pykwasm" -version = "0.1.154" +version = "0.1.155" description = "" readme = "README.md" requires-python = "~=3.10" From 7225499ee849f34433ab4064b036ceac1d447813 Mon Sep 17 00:00:00 2001 From: Burak Bilge Yalcinkaya Date: Wed, 24 Jun 2026 12:51:55 +0300 Subject: [PATCH 11/15] migrate to the new parser --- pykwasm/pyproject.toml | 3 +- pykwasm/src/pykwasm/binary/__init__.py | 1 + pykwasm/src/pykwasm/wasm2kast.py | 397 +----------------- .../tests/integration/test_binary_parser.py | 62 +-- pykwasm/uv.lock | 118 +----- 5 files changed, 15 insertions(+), 566 deletions(-) diff --git a/pykwasm/pyproject.toml b/pykwasm/pyproject.toml index 93762a796..3e48769e6 100644 --- a/pykwasm/pyproject.toml +++ b/pykwasm/pyproject.toml @@ -9,8 +9,7 @@ description = "" readme = "README.md" requires-python = "~=3.10" dependencies = [ - "kframework>=7.1.323", - "py-wasm@git+https://github.com/runtimeverification/py-wasm.git@0.3.0" + "kframework>=7.1.323" ] [[project.authors]] diff --git a/pykwasm/src/pykwasm/binary/__init__.py b/pykwasm/src/pykwasm/binary/__init__.py index e69de29bb..f0fe51e0c 100644 --- a/pykwasm/src/pykwasm/binary/__init__.py +++ b/pykwasm/src/pykwasm/binary/__init__.py @@ -0,0 +1 @@ +from .module import parse_module diff --git a/pykwasm/src/pykwasm/wasm2kast.py b/pykwasm/src/pykwasm/wasm2kast.py index dcfbf1457..3d85aba79 100644 --- a/pykwasm/src/pykwasm/wasm2kast.py +++ b/pykwasm/src/pykwasm/wasm2kast.py @@ -9,49 +9,12 @@ import sys from typing import TYPE_CHECKING -from wasm import instructions -from wasm.datatypes import ( - FunctionIdx, - GlobalIdx, - GlobalType, - MemoryIdx, - MemoryType, - Mutability, - TableIdx, - TableType, - TypeIdx, - ValType, - addresses, -) -from wasm.datatypes.element_segment import ElemModeActive, ElemModeDeclarative, ElemModePassive -from wasm.instructions import InstructionWithPos -from wasm.opcodes import BinaryOpcode -from wasm.parsers import parse_module - -from pykwasm import kwasm_ast as a +from .binary import parse_module if TYPE_CHECKING: - from collections.abc import Iterable from typing import IO from pyk.kast import KInner - from wasm.datatypes import ( - DataSegment, - ElementSegment, - Export, - Function, - FunctionType, - Global, - Import, - Limits, - Memory, - Module, - RefType, - StartFunction, - Table, - ) - from wasm.datatypes.element_segment import ElemMode - from wasm.instructions import BaseInstruction def main(): @@ -65,354 +28,14 @@ def main(): def wasm2kast(wasm_bytes: IO[bytes], filename=None) -> KInner: - """Returns a dictionary representing the Kast JSON.""" - ast = parse_module(wasm_bytes) - return ast2kast(ast, filename=filename) - - -def ast2kast(wasm_ast: Module, filename=None) -> KInner: - """Returns a dictionary representing the Kast JSON.""" - types = a.defns([typ(x) for x in wasm_ast.types]) - funcs = a.defns([func(x) for x in wasm_ast.funcs]) - tables = a.defns([table(x) for x in wasm_ast.tables]) - mems = a.defns([memory(x) for x in wasm_ast.mems]) - globs = a.defns([glob(x) for x in wasm_ast.globals]) - elems = a.defns([elem(x) for x in wasm_ast.elem]) - datas = a.defns([data(x) for x in wasm_ast.data]) - starts = a.defns(start(wasm_ast.start)) - imports = a.defns([imp(x) for x in wasm_ast.imports]) - exports = a.defns([export(x) for x in wasm_ast.exports]) - meta = a.module_metadata(filename=filename) - return a.module( - types=types, - funcs=funcs, - tables=tables, - mems=mems, - globs=globs, - elem=elems, - data=datas, - start=starts, - imports=imports, - exports=exports, - metadata=meta, - ) - - -######### -# Defns # -######### - - -def typ(t: FunctionType): - return a.type(func_type(t.params, t.results)) - - -def func(f: Function): - type = a.KInt(f.type_idx) - ls_list = [val_type(x) for x in f.locals] - locals = a.vec_type(a.val_types(ls_list)) - body = instrs(f.body) - return a.func(type, locals, body) - - -def table(t: Table): - ls = limits(t.type.limits) - typ = ref_type(t.type.elem_type) - return a.table(ls, typ) - - -def memory(m: Memory): - ls = limits(m.type) - return a.memory(ls) - - -def glob(g: Global): - t = global_type(g.type) - init = instrs(g.init) - return a.glob(t, init) - - -def ref_type(t: RefType): - if t is addresses.FunctionAddress: - return a.funcref - if t is addresses.ExternAddress: - return a.externref - raise ValueError(f'Invalid RefType: {t}') - - -def elem_mode(m: ElemMode) -> KInner: - if isinstance(m, ElemModeActive): - offset = instrs(m.offset) - return a.elem_active(m.table, offset) - if isinstance(m, ElemModeDeclarative): - return a.elem_declarative() - if isinstance(m, ElemModePassive): - return a.elem_passive() - raise ValueError(f'Unknown ElemMode: {m}') - - -def elem_init(init: tuple[Iterable[BaseInstruction], ...]) -> Iterable[int | None]: - def expr_to_int(expr: Iterable[BaseInstruction]) -> int | None: - # 'expr' must be a constant expression consisting of a reference instruction - assert len(expr) == 1 or len(expr) == 2 and expr[1].opcode == BinaryOpcode.END, expr - instr = expr[0] - - if isinstance(instr, InstructionWithPos): - instr = instr.instruction - - if isinstance(instr, instructions.RefFunc): - return instr.funcidx - if isinstance(instr, instructions.RefNull): - return None - raise ValueError(f'Invalid reference expression: {expr}') - - return [expr_to_int(e) for e in init] - - -def elem(e: ElementSegment): - typ = ref_type(e.type) - mode = elem_mode(e.mode) - init = elem_init(e.init) - return a.elem(typ, mode, init) - - -def data(d: DataSegment): - offset = instrs(d.offset) - mode = a.datamode_active(d.memory_idx, offset) - return a.data(d.init, mode) - - -def start(s: StartFunction): - if s is None: - return [] - return [a.start(s.function_idx)] - - -def imp(i: Import): - mod_name = a.wasm_string(i.module_name) - name = a.wasm_string(i.as_name) - t = type(i.desc) - if t is TypeIdx: - desc = a.func_desc(i.desc) - elif t is GlobalType: - desc = a.global_desc(global_type(i.desc)) - elif t is TableType: - desc = a.table_desc(limits(i.desc.limits)) - elif t is MemoryType: - desc = a.memory_desc(limits(i.desc)) - return a.imp(mod_name, name, desc) - - -def export(e: Export): - name = a.wasm_string(e.name) - if isinstance(e.desc, FunctionIdx): - idx = a.externidx_func(e.desc) - elif isinstance(e.desc, GlobalIdx): - idx = a.externidx_global(e.desc) - elif isinstance(e.desc, MemoryIdx): - idx = a.externidx_memory(e.desc) - elif isinstance(e.desc, TableIdx): - idx = a.externidx_table(e.desc) - else: - raise ValueError(f'Invalid extern index: {e.desc}') - return a.export(name, idx) - - -########## -# Instrs # -########## - -block_id = 0 - - -def instrs(iis): - """Turn a list of instructions into KAST.""" - # We ignore `END`. - # The AST supplied by py-wasm has already parsed these and terminated the blocks. - # We also ignore `ELSE`. - # The AST supplied by py-wasm includes the statements in the else-branch as part of the `IF` instruction. - return a.instrs([instr(i) for i in iis if not i.opcode == BinaryOpcode.END and not i.opcode == BinaryOpcode.ELSE]) - - -def instr(i): - B = BinaryOpcode - global block_id - # TODO rewrite 'i.opcode == _' conditions as isinstance for better type-checking - if isinstance(i, InstructionWithPos): - inner = instr(i.instruction) - return a.INSTR_WITH_POS(inner, i.offset, i.length) - if i.opcode == B.BLOCK: - cur_block_id = block_id - block_id += 1 - iis = instrs(i.instructions) - res = vec_type(i.result_type) - return a.BLOCK(res, iis, a.KInt(cur_block_id)) - if i.opcode == B.BR: - return a.BR(i.label_idx) - if i.opcode == B.BR_IF: - return a.BR_IF(i.label_idx) - if i.opcode == B.BR_TABLE: - return a.BR_TABLE(i.label_indices, i.default_idx) - if i.opcode == B.CALL: - return a.CALL(i.function_idx) - if i.opcode == B.CALL_INDIRECT: - return a.CALL_INDIRECT(i.table_idx, i.type_idx) - if i.opcode == B.ELSE: - raise (ValueError('ELSE opcode: should have been filtered out.')) - if i.opcode == B.END: - raise (ValueError('End opcode: should have been filtered out.')) - if i.opcode == B.F32_CONST: - return a.F32_CONST(i.value) - if i.opcode == B.F64_CONST: - return a.F64_CONST(i.value) - if i.opcode == B.F32_REINTERPRET_I32: - raise (ValueError('Reinterpret instructions not implemented.')) - if i.opcode == B.F64_REINTERPRET_I64: - raise (ValueError('Reinterpret instructions not implemented.')) - if i.opcode == B.GET_GLOBAL: - return a.GET_GLOBAL(i.global_idx) - if i.opcode == B.GET_LOCAL: - return a.GET_LOCAL(i.local_idx) - if i.opcode == B.I32_CONST: - return a.I32_CONST(i.value) - if i.opcode == B.I64_CONST: - return a.I64_CONST(i.value) - if i.opcode == B.I32_REINTERPRET_F32: - raise (ValueError('Reinterpret instructions not implemented.')) - if i.opcode == B.I64_REINTERPRET_F64: - raise (ValueError('Reinterpret instructions not implemented.')) - if i.opcode == B.IF: - cur_block_id = block_id - block_id += 1 - thens = instrs(i.instructions) - els = instrs(i.else_instructions) - res = vec_type(i.result_type) - return a.IF(res, thens, els, a.KInt(cur_block_id)) - if i.opcode == B.F32_STORE: - return a.F32_STORE(i.memarg.offset) - if i.opcode == B.F64_STORE: - return a.F64_STORE(i.memarg.offset) - if i.opcode == B.I32_STORE: - return a.I32_STORE(i.memarg.offset) - if i.opcode == B.I64_STORE: - return a.I64_STORE(i.memarg.offset) - if i.opcode == B.I32_STORE16: - return a.I32_STORE16(i.memarg.offset) - if i.opcode == B.I64_STORE16: - return a.I64_STORE16(i.memarg.offset) - if i.opcode == B.I32_STORE8: - return a.I32_STORE8(i.memarg.offset) - if i.opcode == B.I64_STORE8: - return a.I64_STORE8(i.memarg.offset) - if i.opcode == B.I64_STORE32: - return a.I64_STORE32(i.memarg.offset) - if i.opcode == B.F32_LOAD: - return a.F32_LOAD(i.memarg.offset) - if i.opcode == B.F64_LOAD: - return a.F64_LOAD(i.memarg.offset) - if i.opcode == B.I32_LOAD: - return a.I32_LOAD(i.memarg.offset) - if i.opcode == B.I64_LOAD: - return a.I64_LOAD(i.memarg.offset) - if i.opcode == B.I32_LOAD16_S: - return a.I32_LOAD16_S(i.memarg.offset) - if i.opcode == B.I32_LOAD16_U: - return a.I32_LOAD16_U(i.memarg.offset) - if i.opcode == B.I64_LOAD16_S: - return a.I64_LOAD16_S(i.memarg.offset) - if i.opcode == B.I64_LOAD16_U: - return a.I64_LOAD16_U(i.memarg.offset) - if i.opcode == B.I32_LOAD8_S: - return a.I32_LOAD8_S(i.memarg.offset) - if i.opcode == B.I32_LOAD8_U: - return a.I32_LOAD8_U(i.memarg.offset) - if i.opcode == B.I64_LOAD8_S: - return a.I64_LOAD8_S(i.memarg.offset) - if i.opcode == B.I64_LOAD8_U: - return a.I64_LOAD8_U(i.memarg.offset) - if i.opcode == B.I64_LOAD32_S: - return a.I64_LOAD32_S(i.memarg.offset) - if i.opcode == B.I64_LOAD32_U: - return a.I64_LOAD32_U(i.memarg.offset) - if i.opcode == B.LOOP: - cur_block_id = block_id - block_id += 1 - iis = instrs(i.instructions) - res = vec_type(i.result_type) - return a.LOOP(res, iis, a.KInt(cur_block_id)) - if i.opcode == B.SET_GLOBAL: - return a.SET_GLOBAL(i.global_idx) - if i.opcode == B.SET_LOCAL: - return a.SET_LOCAL(i.local_idx) - if i.opcode == B.TEE_LOCAL: - return a.TEE_LOCAL(i.local_idx) - if isinstance(i, instructions.RefFunc): - return a.REF_FUNC(i.funcidx) - if isinstance(i, instructions.RefNull): - if i.reftype is addresses.FunctionAddress: - return a.REF_NULL(a.HEAPTYPE_FUNC) - if i.reftype is addresses.ExternAddress: - return a.REF_NULL(a.HEAPTYPE_EXTERN) - raise ValueError(f'Unknown heap type: {i}, {i.reftype}') - if isinstance(i, instructions.TableGet): - return a.TABLE_GET(i.tableidx) - if isinstance(i, instructions.TableSet): - return a.TABLE_SET(i.tableidx) - if isinstance(i, instructions.TableInit): - return a.TABLE_INIT(i.tableidx, i.elemidx) - if isinstance(i, instructions.ElemDrop): - return a.ELEM_DROP(i.elemidx) - if isinstance(i, instructions.TableCopy): - return a.TABLE_COPY(i.tableidx1, i.tableidx2) - if isinstance(i, instructions.TableGrow): - return a.TABLE_GROW(i.tableidx) - if isinstance(i, instructions.TableSize): - return a.TABLE_SIZE(i.tableidx) - if isinstance(i, instructions.TableFill): - return a.TABLE_FILL(i.tableidx) - - # Catch all for operations without direct arguments. - return eval('a.' + i.opcode.name) - - -######## -# Data # -######## - - -def val_type(t: ValType): - if t == ValType.i32: - return a.i32 - if t == ValType.i64: - return a.i64 - if t == ValType.f32: - return a.f32 - if t == ValType.f64: - return a.f64 - if t == ValType.externref: - return a.externref - if t == ValType.funcref: - return a.funcref - raise ValueError(f'Unknown value type: {t}') - - -def vec_type(ts: Iterable[ValType]): - _ts = [val_type(x) for x in ts] - return a.vec_type(a.val_types(_ts)) - - -def func_type(params, results): - pvec = vec_type(params) - rvec = vec_type(results) - return a.func_type(pvec, rvec) - - -def limits(l: Limits): - return (l.min, l.max) + """ + Parse a WebAssembly binary module into its corresponding KAST representation. + Args: + wasm_bytes: A binary IO stream containing the WebAssembly module bytes. + filename: Optional filename associated with the module. Currently unused. -def global_type(t: GlobalType): - vt = val_type(t.valtype) - if t.mut is Mutability.const: - return a.global_type(a.MUT_CONST, vt) - return a.global_type(a.MUT_VAR, vt) + Returns: + A KAST term of sort `ModuleDecl` representing the parsed WebAssembly module. + """ + return parse_module(wasm_bytes) diff --git a/pykwasm/src/tests/integration/test_binary_parser.py b/pykwasm/src/tests/integration/test_binary_parser.py index d15873516..e396317ad 100644 --- a/pykwasm/src/tests/integration/test_binary_parser.py +++ b/pykwasm/src/tests/integration/test_binary_parser.py @@ -7,11 +7,9 @@ from typing import TYPE_CHECKING import pytest -from pyk.kast.inner import KApply, KLabel, KSequence, KSort, Subst +from pyk.kast.inner import KSequence, KSort, Subst from pyk.kast.manip import split_config_from -from pyk.kast.prelude.utils import token -from pykwasm.binary.module import parse_module from pykwasm.wasm2kast import wasm2kast if TYPE_CHECKING: @@ -42,48 +40,6 @@ def test_wasm2kast(krun_llvm: KRun, wat_path: Path) -> None: run_module(krun_llvm, module) -@pytest.mark.parametrize('wat_path', BINARY_WAT_FILES, ids=str) -def test_self_binary_parser(krun_llvm: KRun, wat_path: Path) -> None: - # Given - wat2wasm_cmd = ['wat2wasm', str(wat_path), '--output=/dev/stdout'] - proc_res = run(wat2wasm_cmd, check=True, capture_output=True) - wasm_file = BytesIO(proc_res.stdout) - - assert not proc_res.returncode - - # When - module = parse_module(wasm_file) - - # Then - - # Can convert to Kore successfully - krun_llvm.kast_to_kore(module, KSort('ModuleDecl')) - - # Can run (initiate) successfully - run_module(krun_llvm, module) - - -@pytest.mark.parametrize('wat_path', BINARY_WAT_FILES, ids=str) -def test_diff(krun_llvm: KRun, wat_path: Path) -> None: - # Given - wat2wasm_cmd = ['wat2wasm', str(wat_path), '--output=/dev/stdout'] - proc_res = run(wat2wasm_cmd, check=True, capture_output=True) - - assert not proc_res.returncode - - # When - module_wasm2kast = remove_block_ids(wasm2kast(BytesIO(proc_res.stdout))) - - module_self = parse_module(BytesIO(proc_res.stdout)) - - # Then - assert module_wasm2kast == module_self - - run_wasm2kast = run_module(krun_llvm, module_wasm2kast) - run_self = run_module(krun_llvm, module_self) - assert run_wasm2kast == run_self - - def run_module(krun: KRun, parsed_module: KInner) -> Pattern: try: # Create an initial config @@ -102,19 +58,3 @@ def run_module(krun: KRun, parsed_module: KInner) -> Pattern: except Exception as e: raise Exception('Received error while running') from e - - -def remove_block_ids(k: KInner) -> KInner: - match k: - - case KApply(KLabel('aBlock'), (vec_type, instrs, _)): - return KApply('aBlock', (vec_type, remove_block_ids(instrs), token(0))) - - case KApply(KLabel('aIf'), (vec_type, then_instrs, else_instrs, _)): - return KApply('aIf', (vec_type, remove_block_ids(then_instrs), remove_block_ids(else_instrs), token(0))) - - case KApply(KLabel('aLoop'), (vec_type, instrs, _)): - return KApply(KLabel('aLoop'), (vec_type, remove_block_ids(instrs), token(0))) - - case _: - return k.map_inner(remove_block_ids) diff --git a/pykwasm/uv.lock b/pykwasm/uv.lock index 9c9b73aaa..23487c486 100644 --- a/pykwasm/uv.lock +++ b/pykwasm/uv.lock @@ -426,64 +426,6 @@ toml = [ { name = "tomli", marker = "python_full_version <= '3.11'" }, ] -[[package]] -name = "cytoolz" -version = "0.12.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "toolz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/70/d8/8df71050b214686591241a1826d2e6934b5c295c5bc57f643e4ed697f1eb/cytoolz-0.12.3.tar.gz", hash = "sha256:4503dc59f4ced53a54643272c61dc305d1dbbfbd7d6bdf296948de9f34c3a282", size = 625899, upload-time = "2024-01-25T01:39:13.834Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/11/ebc8b85c77cc2247c169af808901a80e5d29429b3c9b0d114d4048ad2a5a/cytoolz-0.12.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bbe58e26c84b163beba0fbeacf6b065feabc8f75c6d3fe305550d33f24a2d346", size = 413462, upload-time = "2024-01-25T01:36:06.7Z" }, - { url = "https://files.pythonhosted.org/packages/19/22/57f20a9f135a08a6fc9848744c324c4facca2b1370a1ae4e8333e274c707/cytoolz-0.12.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c51b66ada9bfdb88cf711bf350fcc46f82b83a4683cf2413e633c31a64df6201", size = 396040, upload-time = "2024-01-25T01:36:08.623Z" }, - { url = "https://files.pythonhosted.org/packages/12/30/337a18b1182888bf5d59357095a83f537a88e2db32ee0381166a83deddda/cytoolz-0.12.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e70d9c615e5c9dc10d279d1e32e846085fe1fd6f08d623ddd059a92861f4e3dd", size = 1933264, upload-time = "2024-01-25T01:36:10.093Z" }, - { url = "https://files.pythonhosted.org/packages/93/a3/c244bfacd046316cd0a4cffd2e245a8dd9b685168497c3b7c776bdb72c59/cytoolz-0.12.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a83f4532707963ae1a5108e51fdfe1278cc8724e3301fee48b9e73e1316de64f", size = 2014114, upload-time = "2024-01-25T01:36:12.083Z" }, - { url = "https://files.pythonhosted.org/packages/a6/40/51bdfe7fd1cad802eabebb7bb742e694227db25c4806eebf4358229e3220/cytoolz-0.12.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d028044524ee2e815f36210a793c414551b689d4f4eda28f8bbb0883ad78bf5f", size = 1999458, upload-time = "2024-01-25T01:36:14.135Z" }, - { url = "https://files.pythonhosted.org/packages/f7/0b/5973e0ceab96349f9cabb3b20ecc3a717a19e355f68d354dfa538bebb226/cytoolz-0.12.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c2875bcd1397d0627a09a4f9172fa513185ad302c63758efc15b8eb33cc2a98", size = 1956106, upload-time = "2024-01-25T01:36:15.485Z" }, - { url = "https://files.pythonhosted.org/packages/d3/01/bb850ad1467f0d1dc5a21a05e3df6ad308a19d095f07583c4e65a85e2ba8/cytoolz-0.12.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:131ff4820e5d64a25d7ad3c3556f2d8aa65c66b3f021b03f8a8e98e4180dd808", size = 1861974, upload-time = "2024-01-25T01:36:17.185Z" }, - { url = "https://files.pythonhosted.org/packages/c9/97/207e544a2c9c3d8735102403982a319c252c337e9cf792c2753c22cff1b6/cytoolz-0.12.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:04afa90d9d9d18394c40d9bed48c51433d08b57c042e0e50c8c0f9799735dcbd", size = 1937926, upload-time = "2024-01-25T01:36:18.968Z" }, - { url = "https://files.pythonhosted.org/packages/76/1e/6cc756537caef16cd491de1550c1f25140528785e28263326bd3243a19ed/cytoolz-0.12.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:dc1ca9c610425f9854323669a671fc163300b873731584e258975adf50931164", size = 1859141, upload-time = "2024-01-25T01:36:20.62Z" }, - { url = "https://files.pythonhosted.org/packages/20/43/30819bbc4348cabb44d800ab0d0efb29a9d21db8f75060fd1199d5300953/cytoolz-0.12.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:bfa3f8e01bc423a933f2e1c510cbb0632c6787865b5242857cc955cae220d1bf", size = 2013731, upload-time = "2024-01-25T01:36:22.461Z" }, - { url = "https://files.pythonhosted.org/packages/bd/ab/cacabf2d0dfea006575fea900131eb491370ee1664783f80cb131f577001/cytoolz-0.12.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:f702e295dddef5f8af4a456db93f114539b8dc2a7a9bc4de7c7e41d169aa6ec3", size = 2015075, upload-time = "2024-01-25T01:36:24.144Z" }, - { url = "https://files.pythonhosted.org/packages/79/6f/98eeee4f735bff536654625e0c38e50c4b01c13fbd9c744d3284dbccc54d/cytoolz-0.12.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0fbad1fb9bb47e827d00e01992a099b0ba79facf5e5aa453be066033232ac4b5", size = 1953391, upload-time = "2024-01-25T01:36:25.818Z" }, - { url = "https://files.pythonhosted.org/packages/6a/81/80bcbc750e3c8f72d07f1e61c25597fdb23fec3c3d52542f220bff87372c/cytoolz-0.12.3-cp310-cp310-win32.whl", hash = "sha256:8587c3c3dbe78af90c5025288766ac10dc2240c1e76eb0a93a4e244c265ccefd", size = 320881, upload-time = "2024-01-25T01:36:27.405Z" }, - { url = "https://files.pythonhosted.org/packages/45/9e/cce296c6da9404ce210c79700b85d9ba582a6e908ee88ec707efec09bcd9/cytoolz-0.12.3-cp310-cp310-win_amd64.whl", hash = "sha256:9e45803d9e75ef90a2f859ef8f7f77614730f4a8ce1b9244375734567299d239", size = 362216, upload-time = "2024-01-25T01:36:28.625Z" }, - { url = "https://files.pythonhosted.org/packages/98/8a/618a70326d4a52998a6bbb11ca019979891a51b85cbbfce8f9762eec5d2c/cytoolz-0.12.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3ac4f2fb38bbc67ff1875b7d2f0f162a247f43bd28eb7c9d15e6175a982e558d", size = 416356, upload-time = "2024-01-25T01:36:30.344Z" }, - { url = "https://files.pythonhosted.org/packages/bb/17/542f708b9116aa8d8c9c5551500bfa6ab1bddd3edc11457070599a97c197/cytoolz-0.12.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0cf1e1e96dd86829a0539baf514a9c8473a58fbb415f92401a68e8e52a34ecd5", size = 398412, upload-time = "2024-01-25T01:36:32.628Z" }, - { url = "https://files.pythonhosted.org/packages/d6/9c/52a0460f2b59009da63794cfe11ad002d55fc88fced6ccc85f7ae10a3259/cytoolz-0.12.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08a438701c6141dd34eaf92e9e9a1f66e23a22f7840ef8a371eba274477de85d", size = 2090389, upload-time = "2024-01-25T01:36:34.398Z" }, - { url = "https://files.pythonhosted.org/packages/c7/6d/284d4a1f88b1e63b8a1c80162244ca5d4e0b31647c4f56186f3385fd0d0e/cytoolz-0.12.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6b6f11b0d7ed91be53166aeef2a23a799e636625675bb30818f47f41ad31821", size = 2187333, upload-time = "2024-01-25T01:36:36.099Z" }, - { url = "https://files.pythonhosted.org/packages/3e/44/44efcf2acf824aefab5f3fdb97235d360c216c2425db391a8dee687f1774/cytoolz-0.12.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7fde09384d23048a7b4ac889063761e44b89a0b64015393e2d1d21d5c1f534a", size = 2173106, upload-time = "2024-01-25T01:36:37.827Z" }, - { url = "https://files.pythonhosted.org/packages/6f/74/7c2d5af25d51f481e9be771bda2cf5496631cd0fba876d2cc0086caa732d/cytoolz-0.12.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d3bfe45173cc8e6c76206be3a916d8bfd2214fb2965563e288088012f1dabfc", size = 2098805, upload-time = "2024-01-25T01:36:40.09Z" }, - { url = "https://files.pythonhosted.org/packages/e0/e5/9995bf66caee139459ab70d8b5820c37718d5f7e7380481adb85ef522e4d/cytoolz-0.12.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27513a5d5b6624372d63313574381d3217a66e7a2626b056c695179623a5cb1a", size = 1995956, upload-time = "2024-01-25T01:36:41.741Z" }, - { url = "https://files.pythonhosted.org/packages/93/73/eee7a7f455d4691f1915b35d1713ba69c6851f940c6f8fffdd2efcbe4411/cytoolz-0.12.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d294e5e81ff094fe920fd545052ff30838ea49f9e91227a55ecd9f3ca19774a0", size = 2074891, upload-time = "2024-01-25T01:36:43.887Z" }, - { url = "https://files.pythonhosted.org/packages/a3/c7/6f0c60bbb911ee3f33b243a695e2fc944285a1865bbb1dfefee1b7c35d62/cytoolz-0.12.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:727b01a2004ddb513496507a695e19b5c0cfebcdfcc68349d3efd92a1c297bf4", size = 1980228, upload-time = "2024-01-25T01:36:45.42Z" }, - { url = "https://files.pythonhosted.org/packages/58/7c/d7524626b415d410208fd2774ff2824fab4dcbe4a496d53bdba85a1407df/cytoolz-0.12.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:fe1e1779a39dbe83f13886d2b4b02f8c4b10755e3c8d9a89b630395f49f4f406", size = 2147616, upload-time = "2024-01-25T01:36:47.06Z" }, - { url = "https://files.pythonhosted.org/packages/a5/7b/9bdf8615e61958386f5c7108d857312214837ae3eacf3589873499d2f0aa/cytoolz-0.12.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:de74ef266e2679c3bf8b5fc20cee4fc0271ba13ae0d9097b1491c7a9bcadb389", size = 2148775, upload-time = "2024-01-25T01:36:48.813Z" }, - { url = "https://files.pythonhosted.org/packages/45/91/b92ab0e29bf7cfaacfedc85391a0d0365a6ac74363f2532ba307a760db14/cytoolz-0.12.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e04d22049233394e0b08193aca9737200b4a2afa28659d957327aa780ddddf2", size = 2079810, upload-time = "2024-01-25T01:36:50.116Z" }, - { url = "https://files.pythonhosted.org/packages/2f/33/8204d65871fa639d1d3ae788e3afc187f610126f53f2324d4aa37caf0630/cytoolz-0.12.3-cp311-cp311-win32.whl", hash = "sha256:20d36430d8ac809186736fda735ee7d595b6242bdb35f69b598ef809ebfa5605", size = 320212, upload-time = "2024-01-25T01:36:51.73Z" }, - { url = "https://files.pythonhosted.org/packages/ab/e3/7ce2efaf243c9ee75fc730cd018981788b56e7ad3767f24e7a9cd14831ac/cytoolz-0.12.3-cp311-cp311-win_amd64.whl", hash = "sha256:780c06110f383344d537f48d9010d79fa4f75070d214fc47f389357dd4f010b6", size = 363570, upload-time = "2024-01-25T01:36:53.371Z" }, - { url = "https://files.pythonhosted.org/packages/7f/57/b0feabefdff4707d0b96bf75e2298ecb209ba22c2e3fd15e5230dd68441a/cytoolz-0.12.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:86923d823bd19ce35805953b018d436f6b862edd6a7c8b747a13d52b39ed5716", size = 419825, upload-time = "2024-01-25T01:36:54.522Z" }, - { url = "https://files.pythonhosted.org/packages/54/82/795aa9f91ee81818732b435755559857a76e16b7e435e1de434b47ce72b9/cytoolz-0.12.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3e61acfd029bfb81c2c596249b508dfd2b4f72e31b7b53b62e5fb0507dd7293", size = 401673, upload-time = "2024-01-25T01:36:56.435Z" }, - { url = "https://files.pythonhosted.org/packages/23/f3/298f32b2e374f4f716ca74a9e46b64ee1df9f160de262391f7f9bc53ecca/cytoolz-0.12.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd728f4e6051af6af234651df49319da1d813f47894d4c3c8ab7455e01703a37", size = 2096352, upload-time = "2024-01-25T01:36:58.428Z" }, - { url = "https://files.pythonhosted.org/packages/39/44/49929b33bd0cdccc98b281e4ca11f0836b2963c6fdec8ebb7891807ed908/cytoolz-0.12.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe8c6267caa7ec67bcc37e360f0d8a26bc3bdce510b15b97f2f2e0143bdd3673", size = 2161581, upload-time = "2024-01-25T01:36:59.818Z" }, - { url = "https://files.pythonhosted.org/packages/e9/77/980a8bc123490dd7112e1e068da989932b7612504355ddd25593880c3156/cytoolz-0.12.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99462abd8323c52204a2a0ce62454ce8fa0f4e94b9af397945c12830de73f27e", size = 2178057, upload-time = "2024-01-25T01:37:02.23Z" }, - { url = "https://files.pythonhosted.org/packages/23/d9/2f7a4db7f92eab4b567e56cc2fc147fed1ef35f7968e44cefcbbfb366980/cytoolz-0.12.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da125221b1fa25c690fcd030a54344cecec80074df018d906fc6a99f46c1e3a6", size = 2130881, upload-time = "2024-01-25T01:37:03.703Z" }, - { url = "https://files.pythonhosted.org/packages/85/bf/1147229198a8fc3f2090d01263234d47b5b7f8c52c20090b1cceda0929cd/cytoolz-0.12.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c18e351956f70db9e2d04ff02f28e9a41839250d3f936a4c8a1eabd1c3094d2", size = 1979975, upload-time = "2024-01-25T01:37:06.277Z" }, - { url = "https://files.pythonhosted.org/packages/5b/53/b730e9f428e74971f934bf73f0868099d325ffbf0c080a201bb16b6ab508/cytoolz-0.12.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:921e6d2440ac758c4945c587b1d1d9b781b72737ac0c0ca5d5e02ca1db8bded2", size = 2086861, upload-time = "2024-01-25T01:37:07.869Z" }, - { url = "https://files.pythonhosted.org/packages/24/24/df510850c1c57ad69a23a2700083cfd285faf27d16e61f0e503eec122473/cytoolz-0.12.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1651a9bd591a8326329ce1d6336f3129161a36d7061a4d5ea9e5377e033364cf", size = 1973211, upload-time = "2024-01-25T01:37:10.036Z" }, - { url = "https://files.pythonhosted.org/packages/b6/ad/a6e27924a6c8048b4f1ffbd8b9b0b871642bd94d3e457aa42a629c1f667b/cytoolz-0.12.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8893223b87c2782bd59f9c4bd5c7bf733edd8728b523c93efb91d7468b486528", size = 2133017, upload-time = "2024-01-25T01:37:11.684Z" }, - { url = "https://files.pythonhosted.org/packages/26/a3/f9dfb406232d0a100994a86c123259cd2ab023aa8ae325b0d9926f80091f/cytoolz-0.12.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:e4d2961644153c5ae186db964aa9f6109da81b12df0f1d3494b4e5cf2c332ee2", size = 2156771, upload-time = "2024-01-25T01:37:13.62Z" }, - { url = "https://files.pythonhosted.org/packages/fb/bf/ce2c6f6175a29c830904b23faee557de8c167255dbb2bb84e04d7cf5605a/cytoolz-0.12.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:71b6eb97f6695f7ba8ce69c49b707a351c5f46fd97f5aeb5f6f2fb0d6e72b887", size = 2121104, upload-time = "2024-01-25T01:37:16.053Z" }, - { url = "https://files.pythonhosted.org/packages/bf/97/95a5fb2b5db9b1ea8856741a2a94741a71cb5640112403029beb7b3ef1df/cytoolz-0.12.3-cp312-cp312-win32.whl", hash = "sha256:cee3de65584e915053412cd178729ff510ad5f8f585c21c5890e91028283518f", size = 320537, upload-time = "2024-01-25T01:37:18.026Z" }, - { url = "https://files.pythonhosted.org/packages/0a/11/3e69585342ce4e6b7f2f6139c8aa235dab5a4fe5c76a051fe01696618bd0/cytoolz-0.12.3-cp312-cp312-win_amd64.whl", hash = "sha256:9eef0d23035fa4dcfa21e570961e86c375153a7ee605cdd11a8b088c24f707f6", size = 363238, upload-time = "2024-01-25T01:37:19.343Z" }, - { url = "https://files.pythonhosted.org/packages/00/8c/c58e2f14a88132722988ee0951b01ee6cc4880302384f064902d6e8fb048/cytoolz-0.12.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:55f9bd1ae6c2a27eda5abe2a0b65a83029d2385c5a1da7b8ef47af5905d7e905", size = 353101, upload-time = "2024-01-25T01:38:37.808Z" }, - { url = "https://files.pythonhosted.org/packages/35/99/6ef817203004c7055370fa1ab3bb0dfc6b7ebd0b4942099ce86007614001/cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2d271393c378282727f1231d40391ae93b93ddc0997448acc21dd0cb6a1e56d", size = 385225, upload-time = "2024-01-25T01:38:39.382Z" }, - { url = "https://files.pythonhosted.org/packages/a2/25/64e5907b15bd16f0f38e825762540ee3293b513f2eb4b9bb51b7f78d5457/cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee98968d6a66ee83a8ceabf31182189ab5d8598998c8ce69b6d5843daeb2db60", size = 405888, upload-time = "2024-01-25T01:38:40.836Z" }, - { url = "https://files.pythonhosted.org/packages/17/c0/0e19ab05222cf2370d580e5500409c26266f89e853fa18b2b12c530f3957/cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01cfb8518828c1189200c02a5010ea404407fb18fd5589e29c126e84bbeadd36", size = 396781, upload-time = "2024-01-25T01:38:42.349Z" }, - { url = "https://files.pythonhosted.org/packages/5e/39/bee67e2b541ca3478a982c68d7d23b718fa7f2947bdfc0eecdba9c4e0882/cytoolz-0.12.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:456395d7aec01db32bf9e6db191d667347c78d8d48e77234521fa1078f60dabb", size = 340712, upload-time = "2024-01-25T01:38:45.075Z" }, -] - [[package]] name = "exceptiongroup" version = "1.3.1" @@ -997,38 +939,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, ] -[[package]] -name = "numpy" -version = "1.26.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129, upload-time = "2024-02-06T00:26:44.495Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/94/ace0fdea5241a27d13543ee117cbc65868e82213fb31a8eb7fe9ff23f313/numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0", size = 20631468, upload-time = "2024-02-05T23:48:01.194Z" }, - { url = "https://files.pythonhosted.org/packages/20/f7/b24208eba89f9d1b58c1668bc6c8c4fd472b20c45573cb767f59d49fb0f6/numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a", size = 13966411, upload-time = "2024-02-05T23:48:29.038Z" }, - { url = "https://files.pythonhosted.org/packages/fc/a5/4beee6488160798683eed5bdb7eead455892c3b4e1f78d79d8d3f3b084ac/numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4", size = 14219016, upload-time = "2024-02-05T23:48:54.098Z" }, - { url = "https://files.pythonhosted.org/packages/4b/d7/ecf66c1cd12dc28b4040b15ab4d17b773b87fa9d29ca16125de01adb36cd/numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f", size = 18240889, upload-time = "2024-02-05T23:49:25.361Z" }, - { url = "https://files.pythonhosted.org/packages/24/03/6f229fe3187546435c4f6f89f6d26c129d4f5bed40552899fcf1f0bf9e50/numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a", size = 13876746, upload-time = "2024-02-05T23:49:51.983Z" }, - { url = "https://files.pythonhosted.org/packages/39/fe/39ada9b094f01f5a35486577c848fe274e374bbf8d8f472e1423a0bbd26d/numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2", size = 18078620, upload-time = "2024-02-05T23:50:22.515Z" }, - { url = "https://files.pythonhosted.org/packages/d5/ef/6ad11d51197aad206a9ad2286dc1aac6a378059e06e8cf22cd08ed4f20dc/numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07", size = 5972659, upload-time = "2024-02-05T23:50:35.834Z" }, - { url = "https://files.pythonhosted.org/packages/19/77/538f202862b9183f54108557bfda67e17603fc560c384559e769321c9d92/numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5", size = 15808905, upload-time = "2024-02-05T23:51:03.701Z" }, - { url = "https://files.pythonhosted.org/packages/11/57/baae43d14fe163fa0e4c47f307b6b2511ab8d7d30177c491960504252053/numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71", size = 20630554, upload-time = "2024-02-05T23:51:50.149Z" }, - { url = "https://files.pythonhosted.org/packages/1a/2e/151484f49fd03944c4a3ad9c418ed193cfd02724e138ac8a9505d056c582/numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef", size = 13997127, upload-time = "2024-02-05T23:52:15.314Z" }, - { url = "https://files.pythonhosted.org/packages/79/ae/7e5b85136806f9dadf4878bf73cf223fe5c2636818ba3ab1c585d0403164/numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e", size = 14222994, upload-time = "2024-02-05T23:52:47.569Z" }, - { url = "https://files.pythonhosted.org/packages/3a/d0/edc009c27b406c4f9cbc79274d6e46d634d139075492ad055e3d68445925/numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5", size = 18252005, upload-time = "2024-02-05T23:53:15.637Z" }, - { url = "https://files.pythonhosted.org/packages/09/bf/2b1aaf8f525f2923ff6cfcf134ae5e750e279ac65ebf386c75a0cf6da06a/numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a", size = 13885297, upload-time = "2024-02-05T23:53:42.16Z" }, - { url = "https://files.pythonhosted.org/packages/df/a0/4e0f14d847cfc2a633a1c8621d00724f3206cfeddeb66d35698c4e2cf3d2/numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a", size = 18093567, upload-time = "2024-02-05T23:54:11.696Z" }, - { url = "https://files.pythonhosted.org/packages/d2/b7/a734c733286e10a7f1a8ad1ae8c90f2d33bf604a96548e0a4a3a6739b468/numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20", size = 5968812, upload-time = "2024-02-05T23:54:26.453Z" }, - { url = "https://files.pythonhosted.org/packages/3f/6b/5610004206cf7f8e7ad91c5a85a8c71b2f2f8051a0c0c4d5916b76d6cbb2/numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2", size = 15811913, upload-time = "2024-02-05T23:54:53.933Z" }, - { url = "https://files.pythonhosted.org/packages/95/12/8f2020a8e8b8383ac0177dc9570aad031a3beb12e38847f7129bacd96228/numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218", size = 20335901, upload-time = "2024-02-05T23:55:32.801Z" }, - { url = "https://files.pythonhosted.org/packages/75/5b/ca6c8bd14007e5ca171c7c03102d17b4f4e0ceb53957e8c44343a9546dcc/numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b", size = 13685868, upload-time = "2024-02-05T23:55:56.28Z" }, - { url = "https://files.pythonhosted.org/packages/79/f8/97f10e6755e2a7d027ca783f63044d5b1bc1ae7acb12afe6a9b4286eac17/numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b", size = 13925109, upload-time = "2024-02-05T23:56:20.368Z" }, - { url = "https://files.pythonhosted.org/packages/0f/50/de23fde84e45f5c4fda2488c759b69990fd4512387a8632860f3ac9cd225/numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed", size = 17950613, upload-time = "2024-02-05T23:56:56.054Z" }, - { url = "https://files.pythonhosted.org/packages/4c/0c/9c603826b6465e82591e05ca230dfc13376da512b25ccd0894709b054ed0/numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a", size = 13572172, upload-time = "2024-02-05T23:57:21.56Z" }, - { url = "https://files.pythonhosted.org/packages/76/8c/2ba3902e1a0fc1c74962ea9bb33a534bb05984ad7ff9515bf8d07527cadd/numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0", size = 17786643, upload-time = "2024-02-05T23:57:56.585Z" }, - { url = "https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110", size = 5677803, upload-time = "2024-02-05T23:58:08.963Z" }, - { url = "https://files.pythonhosted.org/packages/16/2e/86f24451c2d530c88daf997cb8d6ac622c1d40d19f5a031ed68a4b73a374/numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", size = 15517754, upload-time = "2024-02-05T23:58:36.364Z" }, -] - [[package]] name = "packaging" version = "26.2" @@ -1091,17 +1001,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/05/33/2d74d588408caedd065c2497bdb5ef83ce6082db01289a1e1147f6639802/psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8", size = 249898, upload-time = "2024-01-19T20:47:59.238Z" }, ] -[[package]] -name = "py-wasm" -version = "0.3.0" -source = { git = "https://github.com/runtimeverification/py-wasm.git?rev=0.3.0#07677b120f40b86d5b7839e2dde42889967dcfce" } -dependencies = [ - { name = "cytoolz", marker = "implementation_name == 'cpython'" }, - { name = "mypy-extensions" }, - { name = "numpy" }, - { name = "toolz", marker = "implementation_name == 'pypy'" }, -] - [[package]] name = "pybind11" version = "2.13.6" @@ -1140,11 +1039,10 @@ wheels = [ [[package]] name = "pykwasm" -version = "0.1.154" +version = "0.1.155" source = { editable = "." } dependencies = [ { name = "kframework" }, - { name = "py-wasm" }, ] [package.dev-dependencies] @@ -1167,10 +1065,7 @@ dev = [ ] [package.metadata] -requires-dist = [ - { name = "kframework", specifier = ">=7.1.323" }, - { name = "py-wasm", git = "https://github.com/runtimeverification/py-wasm.git?rev=0.3.0" }, -] +requires-dist = [{ name = "kframework", specifier = ">=7.1.323" }] [package.metadata.requires-dev] dev = [ @@ -1549,15 +1444,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", size = 6675, upload-time = "2025-01-15T12:07:22.074Z" }, ] -[[package]] -name = "toolz" -version = "0.12.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3e/bf/5e12db234df984f6df3c7f12f1428aa680ba4e101f63f4b8b3f9e8d2e617/toolz-0.12.1.tar.gz", hash = "sha256:ecca342664893f177a13dac0e6b41cbd8ac25a358e5f215316d43e2100224f4d", size = 66550, upload-time = "2024-01-24T03:28:28.047Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/8a/d82202c9f89eab30f9fc05380daae87d617e2ad11571ab23d7c13a29bb54/toolz-0.12.1-py3-none-any.whl", hash = "sha256:d22731364c07d72eea0a0ad45bafb2c2937ab6fd38a3507bf55eae8744aa7d85", size = 56121, upload-time = "2024-01-24T03:28:25.97Z" }, -] - [[package]] name = "typing-extensions" version = "4.15.0" From f8040370fe17fcf89b0456cb25046c5f4582c4da Mon Sep 17 00:00:00 2001 From: Burak Bilge Yalcinkaya Date: Wed, 24 Jun 2026 12:59:14 +0300 Subject: [PATCH 12/15] add `#instrWithPos` support --- pykwasm/src/pykwasm/binary/instructions.py | 7 +++++++ pykwasm/src/pykwasm/kwasm_ast.py | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/pykwasm/src/pykwasm/binary/instructions.py b/pykwasm/src/pykwasm/binary/instructions.py index 6d95ad272..366f7e2b4 100644 --- a/pykwasm/src/pykwasm/binary/instructions.py +++ b/pykwasm/src/pykwasm/binary/instructions.py @@ -28,6 +28,13 @@ def instr_seq(terminator: int, s: InputStream) -> list[KInner]: def instr(s: InputStream) -> KInner: + offset = s.tell() + i = _instr(s) + length = s.tell() - offset + return wast.INSTR_WITH_POS(i, offset, length) + + +def _instr(s: InputStream) -> KInner: def blocktype(s: InputStream) -> KInner: if peek_byte(s) == 0x40: skip(1, s) diff --git a/pykwasm/src/pykwasm/kwasm_ast.py b/pykwasm/src/pykwasm/kwasm_ast.py index 05b29e9d0..7fadba0b9 100644 --- a/pykwasm/src/pykwasm/kwasm_ast.py +++ b/pykwasm/src/pykwasm/kwasm_ast.py @@ -195,8 +195,8 @@ def global_type(mut: KInner, valtype: KInner) -> KInner: ########## -def INSTR_WITH_POS(instruction: KInner, start: int, end: int) -> KInner: - return KApply('aInstrWithPos', [instruction, KInt(start), KInt(end)]) +def INSTR_WITH_POS(instruction: KInner, offset: int, length: int) -> KInner: + return KApply('aInstrWithPos', [instruction, KInt(offset), KInt(length)]) ######################## From 315c57c97b40592ae71668acc9902f3ded6a0558 Mon Sep 17 00:00:00 2001 From: devops Date: Wed, 24 Jun 2026 10:05:04 +0000 Subject: [PATCH 13/15] Set Version: 0.1.157 --- package/version | 2 +- pykwasm/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package/version b/package/version index 0214e6937..b0b5ddaf6 100644 --- a/package/version +++ b/package/version @@ -1 +1 @@ -0.1.156 +0.1.157 diff --git a/pykwasm/pyproject.toml b/pykwasm/pyproject.toml index d8e43c673..61f745a3c 100644 --- a/pykwasm/pyproject.toml +++ b/pykwasm/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "pykwasm" -version = "0.1.156" +version = "0.1.157" description = "" readme = "README.md" requires-python = "~=3.10" From c212cbce447a604cb61b93a0281b0c60a57f0ab9 Mon Sep 17 00:00:00 2001 From: Burak Bilge Yalcinkaya Date: Thu, 25 Jun 2026 13:34:55 +0300 Subject: [PATCH 14/15] fix `elem_init` after instrWithPos --- pykwasm/src/pykwasm/binary/module.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pykwasm/src/pykwasm/binary/module.py b/pykwasm/src/pykwasm/binary/module.py index 38b991078..4cb93eb47 100644 --- a/pykwasm/src/pykwasm/binary/module.py +++ b/pykwasm/src/pykwasm/binary/module.py @@ -167,13 +167,18 @@ def elemkind(s: InputStream) -> KInner: def elem_init(init: list[list[KInner]]) -> list[int | None]: def expr_to_int(expr: list[KInner]) -> int | None: # 'expr' must be a constant expression consisting of a reference instruction - match expr: - case [KApply(KLabel('aRef.func'), (KToken(sfuncidx, KSort('Int')),))]: + if len(expr) != 1: + raise WasmParseError(f'Expected a constant reference instruction as elem initializer, got {expr}') + instr = expr[0] + if isinstance(instr, KApply) and instr.label == KLabel('aInstrWithPos'): + instr = instr.args[0] + match instr: + case KApply(KLabel('aRef.func'), (KToken(sfuncidx, KSort('Int')),)): return int(sfuncidx) - case [KApply(KLabel('aRef.null'), _)]: + case KApply(KLabel('aRef.null'), _): return None case _: - raise WasmParseError(f'Expected a constant reference instruction as elem initializer, got {expr}') + raise WasmParseError(f'Expected a constant reference instruction as elem initializer, got {instr}') return [expr_to_int(e) for e in init] From b902f46f9e7f170f69c77ecf712921c76c288e64 Mon Sep 17 00:00:00 2001 From: Burak Bilge Yalcinkaya Date: Thu, 25 Jun 2026 14:34:03 +0300 Subject: [PATCH 15/15] remove unused `BlockMetaData` --- pykwasm/src/pykwasm/binary/instructions.py | 8 +++--- .../pykwasm/kdist/wasm-semantics/wasm-text.md | 8 +++--- .../src/pykwasm/kdist/wasm-semantics/wasm.md | 25 ++++++++----------- pykwasm/src/pykwasm/kwasm_ast.py | 12 ++++----- tests/proofs/loops-spec.k | 9 +++---- 5 files changed, 27 insertions(+), 35 deletions(-) diff --git a/pykwasm/src/pykwasm/binary/instructions.py b/pykwasm/src/pykwasm/binary/instructions.py index 366f7e2b4..c510e1f84 100644 --- a/pykwasm/src/pykwasm/binary/instructions.py +++ b/pykwasm/src/pykwasm/binary/instructions.py @@ -89,18 +89,16 @@ def memarg(s: InputStream) -> tuple[int, int, int]: case 0x02: bt = blocktype(s) ins = instr_seq(0x0B, s) - return wast.BLOCK(bt, wast.instrs(ins), wast.KInt(0)) # TODO either track block ids or deprecate them + return wast.BLOCK(bt, wast.instrs(ins)) case 0x03: bt = blocktype(s) ins = instr_seq(0x0B, s) - return wast.LOOP(bt, wast.instrs(ins), wast.KInt(0)) # TODO either track block ids or deprecate them + return wast.LOOP(bt, wast.instrs(ins)) case 0x04: bt = blocktype(s) in1 = instr_seq(0x05, s) in2 = instr_seq(0x0B, s) - return wast.IF( - bt, wast.instrs(in1), wast.instrs(in2), wast.KInt(0) - ) # TODO either track block ids or deprecate them + return wast.IF(bt, wast.instrs(in1), wast.instrs(in2)) case 0x0C: l = labelidx(s) return wast.BR(l) diff --git a/pykwasm/src/pykwasm/kdist/wasm-semantics/wasm-text.md b/pykwasm/src/pykwasm/kdist/wasm-semantics/wasm-text.md index 3c259aad5..8810a1d8d 100644 --- a/pykwasm/src/pykwasm/kdist/wasm-semantics/wasm-text.md +++ b/pykwasm/src/pykwasm/kdist/wasm-semantics/wasm-text.md @@ -1173,9 +1173,9 @@ There are several formats of block instructions, and the text-to-abstract transf At this point, all branching identifiers should have been resolved, so we can remove the id. ```k - rule #t2aInstr( block _OID:OptionalId TDS:TypeDecls IS end _OID') => #block(gatherTypes(result, TDS), #t2aInstrs(IS), .Int) - rule #t2aInstr( loop _OID:OptionalId TDS IS end _OID') => #loop(gatherTypes(result, TDS), #t2aInstrs(IS), .Int) - rule #t2aInstr( if _OID:OptionalId TDS IS else _OID':OptionalId IS' end _OID'') => #if(gatherTypes(result, TDS), #t2aInstrs(IS), #t2aInstrs(IS'), .Int) + rule #t2aInstr( block _OID:OptionalId TDS:TypeDecls IS end _OID') => #block(gatherTypes(result, TDS), #t2aInstrs(IS)) + rule #t2aInstr( loop _OID:OptionalId TDS IS end _OID') => #loop(gatherTypes(result, TDS), #t2aInstrs(IS)) + rule #t2aInstr( if _OID:OptionalId TDS IS else _OID':OptionalId IS' end _OID'') => #if(gatherTypes(result, TDS), #t2aInstrs(IS), #t2aInstrs(IS')) ``` #### KWasm Administrative Instructions @@ -1186,7 +1186,7 @@ They are currently supported in KWasm text files, but may be deprecated. ```k rule #t2aInstr<_C>(trap) => trap - rule #t2aInstr(#block(VT:VecType, IS:Instrs, BLOCKINFO)) => #block(VT, #t2aInstrs(IS), BLOCKINFO) + rule #t2aInstr(#block(VT:VecType, IS:Instrs)) => #block(VT, #t2aInstrs(IS)) rule #t2aInstr<_>(init_local I V) => init_local I V rule #t2aInstr<_>(init_locals VS) => init_locals VS diff --git a/pykwasm/src/pykwasm/kdist/wasm-semantics/wasm.md b/pykwasm/src/pykwasm/kdist/wasm-semantics/wasm.md index 238f775a3..1ebaf8887 100644 --- a/pykwasm/src/pykwasm/kdist/wasm-semantics/wasm.md +++ b/pykwasm/src/pykwasm/kdist/wasm-semantics/wasm.md @@ -507,12 +507,9 @@ It simply executes the block then records a label with an empty continuation. rule label [ TYPES ] { _ } VALSTACK' => .K ... VALSTACK => #take(lengthValTypes(TYPES), VALSTACK) ++ VALSTACK' - syntax BlockMetaData ::= OptionalInt - // ------------------------------------ - - syntax Instr ::= #block(VecType, Instrs, BlockMetaData) [symbol(aBlock)] - // ------------------------------------------------------------------------ - rule #block(VECTYP, IS, _) => sequenceInstrs(IS) ~> label VECTYP { .Instrs } VALSTACK ... + syntax Instr ::= #block(VecType, Instrs) [symbol(aBlock)] + // --------------------------------------------------------- + rule #block(VECTYP, IS) => sequenceInstrs(IS) ~> label VECTYP { .Instrs } VALSTACK ... VALSTACK => .ValStack ``` @@ -554,19 +551,19 @@ Note that, unlike in the WebAssembly specification document, we do not need the Finally, we have the conditional and loop instructions. ```k - syntax Instr ::= #if( VecType, then : Instrs, else : Instrs, blockInfo: BlockMetaData) [symbol(aIf)] - // ---------------------------------------------------------------------------------------------------- - rule #if(VECTYP, IS, _, _) => sequenceInstrs(IS) ~> label VECTYP { .Instrs } VALSTACK ... + syntax Instr ::= #if( VecType, then : Instrs, else : Instrs) [symbol(aIf)] + // -------------------------------------------------------------------------- + rule #if(VECTYP, IS, _) => sequenceInstrs(IS) ~> label VECTYP { .Instrs } VALSTACK ... < i32 > VAL : VALSTACK => VALSTACK requires VAL =/=Int 0 - rule #if(VECTYP, _, IS, _) => sequenceInstrs(IS) ~> label VECTYP { .Instrs } VALSTACK ... + rule #if(VECTYP, _, IS) => sequenceInstrs(IS) ~> label VECTYP { .Instrs } VALSTACK ... < i32 > VAL : VALSTACK => VALSTACK requires VAL ==Int 0 - syntax Instr ::= #loop(VecType, Instrs, BlockMetaData) [symbol(aLoop)] - // ---------------------------------------------------------------------- - rule #loop(VECTYP, IS, BLOCKMETA) => sequenceInstrs(IS) ~> label VECTYP { #loop(VECTYP, IS, BLOCKMETA) } VALSTACK ... + syntax Instr ::= #loop(VecType, Instrs) [symbol(aLoop)] + // -------------------------------------------------------- + rule #loop(VECTYP, IS) => sequenceInstrs(IS) ~> label VECTYP { #loop(VECTYP, IS) } VALSTACK ... VALSTACK => .ValStack ``` @@ -1223,7 +1220,7 @@ The `#take` function will return the parameter stack in the reversed order, then // ------------------------------------- rule ( invoke FADDR ) => init_locals #revs(#take(lengthValTypes(TDOMAIN), VALSTACK)) ++ #zero(TLOCALS) - ~> #block([TRANGE], INSTRS, .Int) + ~> #block([TRANGE], INSTRS) ~> frame MODIDX TRANGE #drop(lengthValTypes(TDOMAIN), VALSTACK) LOCAL ... diff --git a/pykwasm/src/pykwasm/kwasm_ast.py b/pykwasm/src/pykwasm/kwasm_ast.py index 7fadba0b9..0bf35ae1f 100644 --- a/pykwasm/src/pykwasm/kwasm_ast.py +++ b/pykwasm/src/pykwasm/kwasm_ast.py @@ -207,16 +207,16 @@ def INSTR_WITH_POS(instruction: KInner, offset: int, length: int) -> KInner: UNREACHABLE = KApply('aUnreachable', []) -def BLOCK(vec_type: KInner, instrs: KInner, block_info: KInner) -> KInner: - return KApply('aBlock', [vec_type, instrs, block_info]) +def BLOCK(vec_type: KInner, instrs: KInner) -> KInner: + return KApply('aBlock', [vec_type, instrs]) -def IF(vec_type: KInner, then_instrs: KInner, else_instrs: KInner, block_info: KInner) -> KInner: - return KApply('aIf', [vec_type, then_instrs, else_instrs, block_info]) +def IF(vec_type: KInner, then_instrs: KInner, else_instrs: KInner) -> KInner: + return KApply('aIf', [vec_type, then_instrs, else_instrs]) -def LOOP(vec_type: KInner, instrs: KInner, block_info: KInner) -> KInner: - return KApply('aLoop', [vec_type, instrs, block_info]) +def LOOP(vec_type: KInner, instrs: KInner) -> KInner: + return KApply('aLoop', [vec_type, instrs]) RETURN = KApply('aReturn', []) diff --git a/tests/proofs/loops-spec.k b/tests/proofs/loops-spec.k index 46e9dea07..d1efb8d4b 100644 --- a/tests/proofs/loops-spec.k +++ b/tests/proofs/loops-spec.k @@ -18,8 +18,7 @@ module LOOPS-SPEC #local.tee(0) ITYPE.eqz #br_if(1) - #br(0), - _ + #br(0) ) } .ValStack @@ -51,10 +50,8 @@ module LOOPS-SPEC #local.tee(0) ITYPE.eqz #br_if(1) - #br(0), - _ - ), - _ + #br(0) + ) ) => .K ...