Skip to content

Commit 2e5dda6

Browse files
Adding specific error messaging and enforced maximum duration to WASM calls (#62)
Preventing infinite looping of WASM calls and providing additional details to users when WASM errors occur. --------- Co-authored-by: Ciarán Ryan-Anderson <70174051+qciaran@users.noreply.github.com>
1 parent d9540bd commit 2e5dda6

File tree

18 files changed

+122
-18
lines changed

18 files changed

+122
-18
lines changed

.github/workflows/python-app.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
- name: Pre-commit checks
3333
run: |
3434
python -m pip install pre-commit
35-
pre-commit run --all-files
35+
pre-commit run --all-files --show-diff-on-failure
3636
- name: Test with pytest
3737
run: |
3838
pip install pytest-cov

.pre-commit-config.yaml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
repos:
22
- repo: https://github.com/pre-commit/pre-commit-hooks
3-
rev: v4.5.0
3+
rev: v4.6.0
44
hooks:
55
- id: trailing-whitespace
66
exclude: ^docs/reference/_autosummary/
@@ -15,19 +15,23 @@ repos:
1515
- id: debug-statements
1616

1717
- repo: https://github.com/crate-ci/typos
18-
rev: v1.19.0
18+
rev: v1.21.0
1919
hooks:
2020
- id: typos
2121
args: []
22+
exclude: |
23+
(?x)^(
24+
python/pecos/simulators/cuquantum_old/.*|
25+
)$
2226
2327
- repo: https://github.com/astral-sh/ruff-pre-commit
24-
rev: v0.3.3
28+
rev: v0.4.3
2529
hooks:
2630
- id: ruff
2731
args: [--fix, --exit-non-zero-on-fix]
2832

2933
- repo: https://github.com/psf/black
30-
rev: 24.3.0
34+
rev: 24.4.2
3135
hooks:
3236
- id: black
3337
exclude: |

.typos.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ ba = "ba"
33
datas = "datas"
44
ket = "ket"
55
wqs = "wqs"
6+
thr = "thr"
7+
IY = "IY"

docs/api_guide/quantum_circuits.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ A ``tick`` keyword can be used to specify which tick the gate is discarded from.
167167
Retrieving Information
168168
----------------------
169169

170-
Next, how to retrieve information from a ``QuantumCircuit`` will be dicuss, for example, through attributes or for
170+
Next, how to retrieve information from a ``QuantumCircuit`` will be discussed, for example, through attributes or for
171171
loops.
172172

173173
Number of Ticks

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ build-backend = "setuptools.build_meta"
1515

1616
[project]
1717
name = "quantum-pecos"
18-
version = "0.5.0.dev11"
18+
version = "0.6.0.dev1"
1919
authors = [
2020
{name = "The PECOS Developers"},
2121
]

python/pecos/engines/cvm/wasm.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def eval_cfunc(runner, params, output):
9595
ccop_type = runner.circuit.metadata["ccop_type"]
9696

9797
if ccop is None:
98-
msg = "Wasm not supplied but requested!"
98+
msg = f"Wasm ({ccop_type}) function not found: {func} with args: {args}"
9999
raise MissingCCOPError(msg) from AttributeError
100100

101101
msg = f"Classical coprocessor object not assigned or missing exec method. Wasm-type = {ccop_type}"

python/pecos/engines/cvm/wasm_vms/pywasm.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,7 @@ def exec(self, func, args, debug=False):
4242
args = [int(b) for _, b in args]
4343
return self.p.exec(func, args)
4444

45+
def teardown(self):
46+
pass # Only needed for wasmtime
47+
4548
return PywasmReader(p)

python/pecos/engines/cvm/wasm_vms/pywasm3.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,7 @@ def exec(self, func, args, debug=False):
2828
args = [int(b) for _, b in args]
2929
return self.rt.find_function(func)(*args)
3030

31+
def teardown(self):
32+
pass # Only needed for wasmtime
33+
3134
return Reader(rt)

python/pecos/engines/cvm/wasm_vms/wasmer.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,7 @@ def exec(self, func_name, args, debug=False):
7272
args = [int(b) for _, b in args]
7373
return method(*args)
7474

75+
def teardown(self):
76+
pass # Only needed for wasmtime
77+
7578
return WasmerInstance(path, compiler)

python/pecos/engines/cvm/wasm_vms/wasmtime.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,7 @@ def exec(self, func_name, args, debug=False):
4141
args = [int(b) for _, b in args]
4242
return self.wasmtime.exec(func_name, args)
4343

44+
def teardown(self):
45+
self.wasmtime.teardown()
46+
4447
return WASM(path)

python/pecos/engines/hybrid_engine_old.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ def run(
124124
if output_export:
125125
output = output_export
126126

127+
if self.ccop:
128+
self.ccop.teardown() # Tear down WASM execution context
129+
127130
return output, error_circuits
128131

129132
def run_circuit(self, state, output, output_export, circuit, error_gen, removed_locations=None):

python/pecos/errors.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,13 @@ class NotSupportedGateError(PECOSError):
1717
"""Indicates a gate not supported by a simulator."""
1818

1919

20-
class MissingCCOPError(PECOSError):
20+
class WasmError(PECOSError):
21+
"""Base WASM-related exception type"""
22+
23+
24+
class MissingCCOPError(WasmError):
2125
"""Indicates missing a classical function library."""
26+
27+
28+
class WasmRuntimeError(WasmError):
29+
"""Indicates a runtime WASM error."""
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from threading import Event, Thread
2+
3+
# These values multiplied should equal the intended maximum execution time
4+
WASM_EXECUTION_TICK_LENGTH_S: float = 0.25
5+
WASM_EXECUTION_MAX_TICKS: int = 4
6+
7+
8+
class WasmExecutionTimerThread(Thread):
9+
def __init__(self, stop_event: Event, func) -> None:
10+
Thread.__init__(self, daemon=True)
11+
self._stop_event = stop_event
12+
self._func = func
13+
14+
def run(self):
15+
while not self._stop_event.wait(WASM_EXECUTION_TICK_LENGTH_S):
16+
self._func()

python/pecos/foreign_objects/wasmer.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from wasmer import FunctionType, Instance, Module, Store, engine
1818
from wasmer_compiler_cranelift import Compiler as Cranelift
1919

20+
from pecos.errors import MissingCCOPError, WasmRuntimeError
2021
from pecos.foreign_objects.foreign_object_abc import ForeignObject
2122

2223
if TYPE_CHECKING:
@@ -88,12 +89,21 @@ def get_funcs(self) -> list[str]:
8889
return self.func_names
8990

9091
def exec(self, func_name: str, args: Sequence) -> tuple:
91-
func = getattr(self.instance.exports, func_name)
92+
try:
93+
func = getattr(self.instance.exports, func_name)
94+
except AttributeError as e:
95+
message = f"Func {func_name} not found in WASM"
96+
raise MissingCCOPError(message) from e
97+
9298
params = func.type.params
9399
if len(args) != len(params):
94100
msg = f"Wasmer function `{func_name}` takes {len(params)} args and {len(args)} were given!"
95-
raise TypeError(msg)
96-
return func(*args)
101+
raise WasmRuntimeError(msg)
102+
103+
try:
104+
return func(*args)
105+
except Exception as ex:
106+
raise WasmRuntimeError(ex.args[0]) from ex
97107

98108
def to_dict(self) -> dict:
99109
return {"fobj_class": WasmerObj, "wasm_bytes": self.wasm_bytes}

python/pecos/foreign_objects/wasmtime.py

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,18 @@
1212
from __future__ import annotations
1313

1414
from pathlib import Path
15+
from threading import Event
1516
from typing import TYPE_CHECKING
1617

17-
from wasmtime import FuncType, Instance, Module, Store
18+
from wasmtime import Config, Engine, FuncType, Instance, Module, Store, Trap, TrapCode
1819

20+
from pecos.errors import MissingCCOPError, WasmRuntimeError
1921
from pecos.foreign_objects.foreign_object_abc import ForeignObject
22+
from pecos.foreign_objects.wasm_execution_timer_thread import (
23+
WASM_EXECUTION_MAX_TICKS,
24+
WASM_EXECUTION_TICK_LENGTH_S,
25+
WasmExecutionTimerThread,
26+
)
2027

2128
if TYPE_CHECKING:
2229
from collections.abc import Sequence
@@ -65,8 +72,14 @@ def new_instance(self) -> None:
6572
self.instance = Instance(self.store, self.module, [])
6673

6774
def spin_up_wasm(self) -> None:
68-
self.store = Store()
75+
config = Config()
76+
config.epoch_interruption = True
77+
engine = Engine(config)
78+
self.store = Store(engine)
6979
self.module = Module(self.store.engine, self.wasm_bytes)
80+
self.stop_flag = Event()
81+
self.inc_thread_handle = WasmExecutionTimerThread(self.stop_flag, self._increment_engine)
82+
self.inc_thread_handle.start()
7083
self.new_instance()
7184

7285
def get_funcs(self) -> list[str]:
@@ -80,9 +93,42 @@ def get_funcs(self) -> list[str]:
8093

8194
return self.func_names
8295

96+
def _increment_engine(self):
97+
self.store.engine.increment_epoch()
98+
8399
def exec(self, func_name: str, args: Sequence) -> tuple:
84-
func = self.instance.exports(self.store)[func_name]
85-
return func(self.store, *args)
100+
try:
101+
func = self.instance.exports(self.store)[func_name]
102+
except KeyError as e:
103+
message = f"No method found with name {func_name} in WASM"
104+
raise MissingCCOPError(message) from e
105+
106+
try:
107+
self.store.engine.increment_epoch()
108+
self.store.set_epoch_deadline(WASM_EXECUTION_MAX_TICKS)
109+
output = func(self.store, *args)
110+
return output # noqa: TRY300
111+
except Trap as t:
112+
if t.trap_code is TrapCode.INTERRUPT:
113+
message = (
114+
f"WASM error: WASM failed during run-time. Execution time of "
115+
f"function '{func_name}' exceeded maximum "
116+
f"{WASM_EXECUTION_MAX_TICKS * WASM_EXECUTION_TICK_LENGTH_S}s"
117+
)
118+
else:
119+
message = (
120+
f"WASM error: WASM failed during run-time. Execution of "
121+
f"function '{func_name}' resulted in {t.trap_code}\n"
122+
f"{t.message}"
123+
)
124+
raise WasmRuntimeError(message) from t
125+
except Exception as e:
126+
message = f"Error during execution of function '{func_name}' with args {args}"
127+
raise WasmRuntimeError(message) from e
128+
129+
def teardown(self) -> None:
130+
self.stop_flag.set()
131+
self.inc_thread_handle.join()
86132

87133
def to_dict(self) -> dict:
88134
return {"fobj_class": WasmtimeObj, "wasm_bytes": self.wasm_bytes}

ruff.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ ignore = [
112112
# TODO: Remove to improve error handling...
113113
"TRY002", # Custom exceptions
114114
"TRY003", # Avoid specifying long messages outside the exception class
115+
116+
# UP031 should be enabled after fixing all suggestions in a separate PR
117+
"UP031",
115118
]
116119

117120
[lint.per-file-ignores]

tests/integration/state_sim_tests/test_cointoss.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2023 The PECOS Developers
1+
# Copyright 2024 The PECOS Developers
22
#
33
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
44
# the License.You may obtain a copy of the License at

tests/integration/state_sim_tests/test_statevec.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2023 The PECOS Developers
1+
# Copyright 2024 The PECOS Developers
22
#
33
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
44
# the License.You may obtain a copy of the License at

0 commit comments

Comments
 (0)