mplang-nightly 0.1.dev143__py3-none-any.whl → 0.1.dev144__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,10 +11,3 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
-
15
- """
16
- Backend module for mplang.
17
-
18
- This module contains handlers that execute serialized functions on individual
19
- parties in a multi-party computation system.
20
- """
mplang/backend/base.py CHANGED
@@ -12,12 +12,20 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- """Flat backend kernel registry & per-participant runtime.
15
+ """Backend kernel registry & per-participant runtime (explicit op->kernel binding).
16
16
 
17
- Design revision:
18
- - Global, stateless kernel function catalog (fn_type -> callable).
19
- - BackendRuntime: per-rank state & cache; executes kernels.
20
- - Legacy global helpers removed after full migration to explicit runtimes.
17
+ This version decouples *kernel implementation registration* from *operation binding*.
18
+
19
+ Concepts:
20
+ * kernel_id: unique identifier of a concrete backend implementation.
21
+ * op_type: semantic operation name carried by ``PFunction.fn_type``.
22
+ * bind_op(op_type, kernel_id): performed by higher layer (see ``backend.context``)
23
+ to select which implementation handles an op. Runtime dispatch is now a 2-step:
24
+ pfunc.fn_type -> active kernel_id -> KernelSpec.fn
25
+
26
+ The previous implicit "import == register+bind" coupling is removed. Kernel
27
+ modules only call ``@kernel_def(kernel_id)``. Default bindings are established
28
+ centrally (lazy) the first time a runtime executes a kernel.
21
29
  """
22
30
 
23
31
  from __future__ import annotations
@@ -27,22 +35,17 @@ from collections.abc import Callable
27
35
  from dataclasses import dataclass
28
36
  from typing import Any
29
37
 
30
- from mplang.core.dtype import UINT8, DType
31
- from mplang.core.pfunc import PFunction
32
- from mplang.core.table import TableLike, TableType
33
- from mplang.core.tensor import TensorLike, TensorType
34
-
35
38
  __all__ = [
36
- "BackendRuntime",
37
39
  "KernelContext",
38
- "create_runtime",
40
+ "KernelSpec",
41
+ "bind_op",
39
42
  "cur_kctx",
40
- "kernel_def",
41
- "list_registered_kernels",
43
+ "get_kernel_for_op",
44
+ "list_kernels",
45
+ "list_ops",
46
+ "unbind_op",
42
47
  ]
43
48
 
44
- # ---------------- Context ----------------
45
-
46
49
 
47
50
  @dataclass
48
51
  class KernelContext:
@@ -99,189 +102,74 @@ def cur_kctx() -> KernelContext:
99
102
 
100
103
  # ---------------- Registry ----------------
101
104
 
102
- # Canonical kernel callable signature (new style): (pfunc, *args) -> Any | sequence
103
- # - No **kwargs (explicitly disallowed)
104
- # - Return normalization handled by BackendRuntime.run_kernel
105
+ # Kernel callable signature: (pfunc, *args) -> Any | sequence (no **kwargs)
105
106
  KernelFn = Callable[..., Any]
106
107
 
107
- _KERNELS: dict[str, KernelFn] = {}
108
108
 
109
+ @dataclass
110
+ class KernelSpec:
111
+ kernel_id: str
112
+ fn: KernelFn
113
+ meta: dict[str, Any]
109
114
 
110
- def _validate_table_arg(
111
- fn_type: str, arg_index: int, spec: TableType, value: Any
112
- ) -> None:
113
- if not isinstance(value, TableLike):
114
- raise TypeError(
115
- f"kernel {fn_type} input[{arg_index}] expects TableLike, got {type(value).__name__}"
116
- )
117
- if len(value.columns) != len(spec.columns):
118
- raise ValueError(
119
- f"kernel {fn_type} input[{arg_index}] column count mismatch: got {len(value.columns)}, expected {len(spec.columns)}"
120
- )
121
115
 
116
+ # All registered kernel implementations: kernel_id -> spec
117
+ _KERNELS: dict[str, KernelSpec] = {}
118
+
119
+ # Active op bindings: op_type -> kernel_id
120
+ _BINDINGS: dict[str, str] = {}
122
121
 
123
- def _validate_tensor_arg(
124
- fn_type: str, arg_index: int, spec: TensorType, value: Any
125
- ) -> None:
126
- # Backend-only handle sentinel (e.g., PHE keys) bypasses all structural checks
127
- if tuple(spec.shape) == (-1, 0) and spec.dtype == UINT8:
128
- return
129
122
 
130
- if isinstance(value, (int, float, bool, complex)):
131
- val_shape: tuple[Any, ...] = ()
132
- duck_dtype: Any = type(value)
133
- else:
134
- if not isinstance(value, TensorLike):
135
- raise TypeError(
136
- f"kernel {fn_type} input[{arg_index}] expects TensorLike, got {type(value).__name__}"
137
- )
138
- val_shape = getattr(value, "shape", ())
139
- duck_dtype = getattr(value, "dtype", None)
140
-
141
- if len(spec.shape) != len(val_shape):
142
- raise ValueError(
143
- f"kernel {fn_type} input[{arg_index}] rank mismatch: got {val_shape}, expected {spec.shape}"
144
- )
145
-
146
- for dim_idx, (spec_dim, val_dim) in enumerate(
147
- zip(spec.shape, val_shape, strict=True)
148
- ):
149
- if spec_dim >= 0 and spec_dim != val_dim:
150
- raise ValueError(
151
- f"kernel {fn_type} input[{arg_index}] shape mismatch at dim {dim_idx}: got {val_dim}, expected {spec_dim}"
152
- )
153
-
154
- try:
155
- val_dtype = DType.from_any(duck_dtype)
156
- except (ValueError, TypeError): # pragma: no cover
157
- raise TypeError(
158
- f"kernel {fn_type} input[{arg_index}] has unsupported dtype object {duck_dtype!r}"
159
- ) from None
160
- if val_dtype != spec.dtype:
161
- raise ValueError(
162
- f"kernel {fn_type} input[{arg_index}] dtype mismatch: got {val_dtype}, expected {spec.dtype}"
163
- )
164
-
165
-
166
- def kernel_def(fn_type: str) -> Callable[[KernelFn], KernelFn]:
167
- """Decorator to register a backend kernel (new signature).
168
-
169
- Expected Python signature form:
170
-
171
- @kernel_def("namespace.op")
172
- def _op(pfunc: PFunction, *args): ...
173
-
174
- Rules:
175
- * First parameter MUST be the PFunction object.
176
- * Positional arguments correspond 1:1 to pfunc.ins_info order.
177
- * **kwargs are NOT supported (will raise at call site if used).
178
- * Return value forms accepted (n = len(pfunc.outs_info)):
179
- - n == 0: return None / () / []
180
- - n == 1: return scalar/object OR (value,) / [value]
181
- - n > 1 : return tuple/list of length n
182
- Anything else raises a ValueError.
123
+ def kernel_def(kernel_id: str, /, **meta: Any) -> Callable[[KernelFn], KernelFn]:
124
+ """Decorator to register a concrete kernel implementation.
125
+
126
+ This ONLY registers the implementation (kernel_id -> fn). It does NOT bind
127
+ any op. Higher layer must call ``bind_op(op_type, kernel_id)`` explicitly.
183
128
  """
184
129
 
185
130
  def _decorator(fn: KernelFn) -> KernelFn:
186
- if fn_type in _KERNELS:
187
- raise ValueError(f"duplicate backend kernel fn_type={fn_type}")
188
- _KERNELS[fn_type] = fn
131
+ if kernel_id in _KERNELS:
132
+ raise ValueError(f"duplicate kernel_id={kernel_id}")
133
+ _KERNELS[kernel_id] = KernelSpec(kernel_id=kernel_id, fn=fn, meta=dict(meta))
189
134
  return fn
190
135
 
191
136
  return _decorator
192
137
 
193
138
 
194
- def list_registered_kernels() -> list[str]: # public API unchanged
195
- return sorted(_KERNELS.keys())
139
+ def bind_op(op_type: str, kernel_id: str, *, force: bool = True) -> None:
140
+ """Bind an op_type to a registered kernel implementation.
196
141
 
142
+ Args:
143
+ op_type: Semantic operation name.
144
+ kernel_id: Previously registered kernel identifier.
145
+ force: If False and op_type already bound, keep existing binding.
146
+ If True (default), overwrite.
147
+ """
148
+ if kernel_id not in _KERNELS:
149
+ raise KeyError(f"kernel_id {kernel_id} not registered")
150
+ if not force and op_type in _BINDINGS:
151
+ return
152
+ _BINDINGS[op_type] = kernel_id
197
153
 
198
- class BackendRuntime:
199
- """Per-rank backend execution environment.
200
154
 
201
- Holds mutable backend state (namespaced pockets) and a cache. Stateless
202
- kernel implementations look up their state through cur_kctx().
203
- """
155
+ def unbind_op(op_type: str) -> None:
156
+ _BINDINGS.pop(op_type, None)
157
+
158
+
159
+ def get_kernel_for_op(op_type: str) -> KernelSpec:
160
+ kid = _BINDINGS.get(op_type)
161
+ if kid is None:
162
+ # Tests expect NotImplementedError for unsupported operations
163
+ raise NotImplementedError(f"no backend kernel registered for op {op_type}")
164
+ spec = _KERNELS.get(kid)
165
+ if spec is None: # inconsistent state
166
+ raise RuntimeError(f"active kernel_id {kid} missing spec")
167
+ return spec
168
+
169
+
170
+ def list_kernels() -> list[str]:
171
+ return sorted(_KERNELS.keys())
172
+
204
173
 
205
- def __init__(self, rank: int, world_size: int):
206
- self.rank = rank
207
- self.world_size = world_size
208
- self.state: dict[str, dict[str, Any]] = {}
209
- self.cache: dict[str, Any] = {}
210
-
211
- # Main entry
212
- def run_kernel(self, pfunc: PFunction, arg_list: list[Any]) -> list[Any]:
213
- fn_type = pfunc.fn_type
214
- fn = _KERNELS.get(fn_type)
215
- if fn is None:
216
- raise NotImplementedError(f"no backend kernel registered for {fn_type}")
217
-
218
- # Strict positional arg count validation (no kernel-managed arity bypass)
219
- if len(arg_list) != len(pfunc.ins_info):
220
- raise ValueError(
221
- f"kernel {fn_type} arg count mismatch: got {len(arg_list)}, expect {len(pfunc.ins_info)}"
222
- )
223
-
224
- for idx, (spec, val) in enumerate(zip(pfunc.ins_info, arg_list, strict=True)):
225
- if isinstance(spec, TableType):
226
- _validate_table_arg(fn_type, idx, spec, val)
227
- continue
228
-
229
- if isinstance(spec, TensorType):
230
- _validate_tensor_arg(fn_type, idx, spec, val)
231
- continue
232
-
233
- # Unknown spec type: silently skip validation (legacy behavior)
234
- continue
235
-
236
- kctx = KernelContext(
237
- rank=self.rank,
238
- world_size=self.world_size,
239
- state=self.state,
240
- cache=self.cache,
241
- )
242
- token = _CTX_VAR.set(kctx)
243
- try:
244
- raw = fn(pfunc, *arg_list)
245
- finally:
246
- _CTX_VAR.reset(token)
247
-
248
- # Normalize return values
249
- expected = len(pfunc.outs_info)
250
- if expected == 0:
251
- if raw in (None, (), []):
252
- return []
253
- raise ValueError(
254
- f"kernel {fn_type} should return no values; got {type(raw).__name__}"
255
- )
256
-
257
- # If multi-output expected, raw must be sequence of right length
258
- if expected == 1:
259
- if isinstance(raw, (tuple, list)):
260
- if len(raw) != 1:
261
- raise ValueError(
262
- f"kernel {fn_type} produced {len(raw)} outputs, expected 1"
263
- )
264
- return [raw[0]]
265
- # Single object
266
- return [raw]
267
-
268
- # expected > 1
269
- if not isinstance(raw, (tuple, list)):
270
- raise TypeError(
271
- f"kernel {fn_type} must return sequence (len={expected}), got {type(raw).__name__}"
272
- )
273
- if len(raw) != expected:
274
- raise ValueError(
275
- f"kernel {fn_type} produced {len(raw)} outputs, expected {expected}"
276
- )
277
- return list(raw)
278
-
279
- # Optional helper
280
- def reset(self) -> None: # pragma: no cover - simple
281
- self.state.clear()
282
- self.cache.clear()
283
-
284
-
285
- def create_runtime(rank: int, world_size: int) -> BackendRuntime:
286
- """Factory for BackendRuntime (allows future policy injection)."""
287
- return BackendRuntime(rank, world_size)
174
+ def list_ops() -> list[str]:
175
+ return sorted(_BINDINGS.keys())
@@ -0,0 +1,255 @@
1
+ # Copyright 2025 Ant Group Co., Ltd.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ from collections.abc import Mapping
18
+ from dataclasses import dataclass, field
19
+ from typing import Any
20
+
21
+ from mplang.backend import base
22
+ from mplang.backend.base import KernelContext, bind_op, get_kernel_for_op
23
+ from mplang.core.dtype import UINT8, DType
24
+ from mplang.core.pfunc import PFunction
25
+ from mplang.core.table import TableLike, TableType
26
+ from mplang.core.tensor import TensorLike, TensorType
27
+
28
+ # Default bindings
29
+ # Import kernel implementation modules explicitly so their @kernel_def entries
30
+ # register at import time. Keep imports grouped; alias with leading underscore
31
+ # to silence unused variable warnings without F401 pragmas.
32
+ _IMPL_IMPORTED = False
33
+
34
+
35
+ def _ensure_impl_imported() -> None:
36
+ global _IMPL_IMPORTED
37
+ if _IMPL_IMPORTED:
38
+ return
39
+ from mplang.backend import builtin as _impl_builtin # noqa: F401
40
+ from mplang.backend import crypto as _impl_crypto # noqa: F401
41
+ from mplang.backend import phe as _impl_phe # noqa: F401
42
+ from mplang.backend import spu as _impl_spu # noqa: F401
43
+ from mplang.backend import sql_duckdb as _impl_sql_duckdb # noqa: F401
44
+ from mplang.backend import stablehlo as _impl_stablehlo # noqa: F401
45
+ from mplang.backend import tee as _impl_tee # noqa: F401
46
+
47
+ _IMPL_IMPORTED = True
48
+
49
+
50
+ # imports consolidated above
51
+
52
+ _DEFAULT_BINDINGS: dict[str, str] = {
53
+ # builtin
54
+ "builtin.identity": "builtin.identity",
55
+ "builtin.read": "builtin.read",
56
+ "builtin.write": "builtin.write",
57
+ "builtin.constant": "builtin.constant",
58
+ "builtin.rank": "builtin.rank",
59
+ "builtin.prand": "builtin.prand",
60
+ "builtin.table_to_tensor": "builtin.table_to_tensor",
61
+ "builtin.tensor_to_table": "builtin.tensor_to_table",
62
+ "builtin.debug_print": "builtin.debug_print",
63
+ "builtin.pack": "builtin.pack",
64
+ "builtin.unpack": "builtin.unpack",
65
+ # crypto
66
+ "crypto.keygen": "crypto.keygen",
67
+ "crypto.enc": "crypto.enc",
68
+ "crypto.dec": "crypto.dec",
69
+ "crypto.kem_keygen": "crypto.kem_keygen",
70
+ "crypto.kem_derive": "crypto.kem_derive",
71
+ "crypto.hkdf": "crypto.hkdf",
72
+ # phe
73
+ "phe.keygen": "phe.keygen",
74
+ "phe.encrypt": "phe.encrypt",
75
+ "phe.mul": "phe.mul",
76
+ "phe.add": "phe.add",
77
+ "phe.decrypt": "phe.decrypt",
78
+ "phe.dot": "phe.dot",
79
+ "phe.gather": "phe.gather",
80
+ "phe.scatter": "phe.scatter",
81
+ "phe.concat": "phe.concat",
82
+ "phe.reshape": "phe.reshape",
83
+ "phe.transpose": "phe.transpose",
84
+ # spu
85
+ "spu.seed_env": "spu.seed_env",
86
+ "spu.makeshares": "spu.makeshares",
87
+ "spu.reconstruct": "spu.reconstruct",
88
+ "spu.run_pphlo": "spu.run_pphlo",
89
+ # stablehlo
90
+ "mlir.stablehlo": "mlir.stablehlo",
91
+ # sql
92
+ # generic SQL op; backend-specific kernel id for duckdb
93
+ "sql.run": "duckdb.run_sql",
94
+ # tee
95
+ "tee.quote": "tee.quote",
96
+ "tee.attest": "tee.attest",
97
+ }
98
+
99
+
100
+ # --- RuntimeContext ---
101
+
102
+
103
+ @dataclass
104
+ class RuntimeContext:
105
+ rank: int
106
+ world_size: int
107
+ bindings: Mapping[str, str] | None = None # optional overrides
108
+ state: dict[str, dict[str, Any]] = field(default_factory=dict)
109
+ cache: dict[str, Any] = field(default_factory=dict)
110
+ stats: dict[str, Any] = field(default_factory=dict)
111
+
112
+ def __post_init__(self) -> None:
113
+ _ensure_impl_imported()
114
+ if self.bindings is not None:
115
+ for op, kid in self.bindings.items():
116
+ bind_op(op, kid)
117
+ else:
118
+ for op, kid in _DEFAULT_BINDINGS.items():
119
+ bind_op(op, kid)
120
+ # Initialize stats pocket
121
+ self.stats.setdefault("op_calls", {})
122
+
123
+ def run_kernel(self, pfunc: PFunction, arg_list: list[Any]) -> list[Any]:
124
+ fn_type = pfunc.fn_type
125
+ spec = get_kernel_for_op(fn_type)
126
+ fn = spec.fn
127
+ if len(arg_list) != len(pfunc.ins_info):
128
+ raise ValueError(
129
+ f"kernel {fn_type} arg count mismatch: got {len(arg_list)}, expect {len(pfunc.ins_info)}"
130
+ )
131
+ for idx, (ins_spec, val) in enumerate(
132
+ zip(pfunc.ins_info, arg_list, strict=True)
133
+ ):
134
+ if isinstance(ins_spec, TableType):
135
+ _validate_table_arg(fn_type, idx, ins_spec, val)
136
+ continue
137
+ if isinstance(ins_spec, TensorType):
138
+ _validate_tensor_arg(fn_type, idx, ins_spec, val)
139
+ continue
140
+ # install kernel context
141
+ kctx = KernelContext(
142
+ rank=self.rank,
143
+ world_size=self.world_size,
144
+ state=self.state,
145
+ cache=self.cache,
146
+ )
147
+ token = base._CTX_VAR.set(kctx) # type: ignore[attr-defined]
148
+ try:
149
+ raw = fn(pfunc, *arg_list)
150
+ finally:
151
+ base._CTX_VAR.reset(token) # type: ignore[attr-defined]
152
+ # Stats (best effort)
153
+ try:
154
+ op_calls = self.stats.setdefault("op_calls", {})
155
+ op_calls[fn_type] = op_calls.get(fn_type, 0) + 1
156
+ except Exception: # pragma: no cover - never raise due to stats
157
+ pass
158
+ expected = len(pfunc.outs_info)
159
+ if expected == 0:
160
+ if raw in (None, (), []):
161
+ return []
162
+ raise ValueError(
163
+ f"kernel {fn_type} should return no values; got {type(raw).__name__}"
164
+ )
165
+ if expected == 1:
166
+ if isinstance(raw, (tuple, list)):
167
+ if len(raw) != 1:
168
+ raise ValueError(
169
+ f"kernel {fn_type} produced {len(raw)} outputs, expected 1"
170
+ )
171
+ return [raw[0]]
172
+ return [raw]
173
+ if not isinstance(raw, (tuple, list)):
174
+ raise TypeError(
175
+ f"kernel {fn_type} must return sequence (len={expected}), got {type(raw).__name__}"
176
+ )
177
+ if len(raw) != expected:
178
+ raise ValueError(
179
+ f"kernel {fn_type} produced {len(raw)} outputs, expected {expected}"
180
+ )
181
+ return list(raw)
182
+
183
+ def reset(self) -> None:
184
+ self.state.clear()
185
+ self.cache.clear()
186
+
187
+ # ---- explicit (re)binding API ----
188
+ def bind_op(self, op_type: str, kernel_id: str, *, force: bool = False) -> None:
189
+ """Bind an operation to a kernel at runtime.
190
+
191
+ force=False (default) preserves any existing binding to avoid accidental
192
+ silent overrides. Use ``rebind_op`` or ``force=True`` to intentionally
193
+ change a binding.
194
+ """
195
+ base.bind_op(op_type, kernel_id, force=force)
196
+
197
+ def rebind_op(self, op_type: str, kernel_id: str) -> None:
198
+ """Force rebind an operation to a different kernel (shorthand)."""
199
+ base.bind_op(op_type, kernel_id, force=True)
200
+
201
+
202
+ def _validate_table_arg(
203
+ fn_type: str, arg_index: int, spec: TableType, value: Any
204
+ ) -> None:
205
+ if not isinstance(value, TableLike):
206
+ raise TypeError(
207
+ f"kernel {fn_type} input[{arg_index}] expects TableLike, got {type(value).__name__}"
208
+ )
209
+ if len(value.columns) != len(spec.columns):
210
+ raise ValueError(
211
+ f"kernel {fn_type} input[{arg_index}] column count mismatch: got {len(value.columns)}, expected {len(spec.columns)}"
212
+ )
213
+
214
+
215
+ def _validate_tensor_arg(
216
+ fn_type: str, arg_index: int, spec: TensorType, value: Any
217
+ ) -> None:
218
+ # Backend-only handle sentinel (e.g., PHE keys) bypasses all structural checks
219
+ if tuple(spec.shape) == (-1, 0) and spec.dtype == UINT8:
220
+ return
221
+
222
+ if isinstance(value, (int, float, bool, complex)):
223
+ val_shape: tuple[Any, ...] = ()
224
+ duck_dtype: Any = type(value)
225
+ else:
226
+ if not isinstance(value, TensorLike):
227
+ raise TypeError(
228
+ f"kernel {fn_type} input[{arg_index}] expects TensorLike, got {type(value).__name__}"
229
+ )
230
+ val_shape = getattr(value, "shape", ())
231
+ duck_dtype = getattr(value, "dtype", None)
232
+
233
+ if len(spec.shape) != len(val_shape):
234
+ raise ValueError(
235
+ f"kernel {fn_type} input[{arg_index}] rank mismatch: got {val_shape}, expected {spec.shape}"
236
+ )
237
+
238
+ for dim_idx, (spec_dim, val_dim) in enumerate(
239
+ zip(spec.shape, val_shape, strict=True)
240
+ ):
241
+ if spec_dim >= 0 and spec_dim != val_dim:
242
+ raise ValueError(
243
+ f"kernel {fn_type} input[{arg_index}] shape mismatch at dim {dim_idx}: got {val_dim}, expected {spec_dim}"
244
+ )
245
+
246
+ try:
247
+ val_dtype = DType.from_any(duck_dtype)
248
+ except (ValueError, TypeError): # pragma: no cover
249
+ raise TypeError(
250
+ f"kernel {fn_type} input[{arg_index}] has unsupported dtype object {duck_dtype!r}"
251
+ ) from None
252
+ if val_dtype != spec.dtype:
253
+ raise ValueError(
254
+ f"kernel {fn_type} input[{arg_index}] dtype mismatch: got {val_dtype}, expected {spec.dtype}"
255
+ )
mplang/backend/spu.py CHANGED
@@ -186,16 +186,18 @@ def _spu_reconstruct(pfunc: PFunction, *args: Any) -> Any:
186
186
  return reconstructed
187
187
 
188
188
 
189
- @kernel_def("mlir.pphlo")
189
+ @kernel_def("spu.run_pphlo")
190
190
  def _spu_run_mlir(pfunc: PFunction, *args: Any) -> Any:
191
- """Execute compiled SPU function (mlir.pphlo) and return SpuValue outputs.
191
+ """Execute compiled SPU function (spu.run_pphlo) and return SpuValue outputs.
192
192
 
193
193
  Participation rule: a rank participates iff its entry in the stored
194
194
  link_ctx list is non-None. This allows us to allocate a world-sized list
195
195
  (indexed by global rank) and simply assign None for non-SPU parties.
196
196
  """
197
- if pfunc.fn_type != "mlir.pphlo":
198
- raise ValueError(f"Unsupported format: {pfunc.fn_type}. Expected 'mlir.pphlo'")
197
+ if pfunc.fn_type != "spu.run_pphlo":
198
+ raise ValueError(
199
+ f"Unsupported format: {pfunc.fn_type}. Expected 'spu.run_pphlo'"
200
+ )
199
201
 
200
202
  cfg, _ = _get_spu_config_and_world()
201
203
  pocket = _get_spu_pocket()
@@ -20,7 +20,7 @@ from mplang.backend.base import kernel_def
20
20
  from mplang.core.pfunc import PFunction
21
21
 
22
22
 
23
- @kernel_def("sql[duckdb]")
23
+ @kernel_def("duckdb.run_sql")
24
24
  def _duckdb_sql(pfunc: PFunction, *args: Any) -> Any:
25
25
  import duckdb
26
26
  import pandas as pd
@@ -27,7 +27,7 @@ from __future__ import annotations
27
27
  from dataclasses import dataclass
28
28
  from typing import Any, Protocol
29
29
 
30
- from mplang.backend.base import BackendRuntime
30
+ from mplang.backend.context import RuntimeContext
31
31
  from mplang.core.comm import ICommunicator
32
32
  from mplang.core.expr.ast import (
33
33
  AccessExpr,
@@ -56,7 +56,7 @@ class IEvaluator(Protocol):
56
56
  backend state via evaluator.runtime.run_kernel(...).
57
57
  """
58
58
 
59
- runtime: BackendRuntime
59
+ runtime: RuntimeContext
60
60
 
61
61
  def evaluate(self, root: Expr, env: dict[str, Any] | None = None) -> list[Any]: ...
62
62
 
@@ -72,7 +72,7 @@ class EvalSemantic:
72
72
  rank: int
73
73
  env: dict[str, Any]
74
74
  comm: ICommunicator
75
- runtime: BackendRuntime
75
+ runtime: RuntimeContext
76
76
 
77
77
  # ------------------------------ Shared helpers (semantics) ------------------------------
78
78
  def _should_run(self, rmask: Mask | None, args: list[Any]) -> bool:
@@ -205,7 +205,7 @@ class RecursiveEvaluator(EvalSemantic, ExprVisitor):
205
205
  rank: int,
206
206
  env: dict[str, Any],
207
207
  comm: ICommunicator,
208
- runtime: BackendRuntime,
208
+ runtime: RuntimeContext,
209
209
  ) -> None:
210
210
  super().__init__(rank, env, comm, runtime)
211
211
  self._cache: dict[int, Any] = {} # Cache based on expr id
@@ -380,7 +380,7 @@ class IterativeEvaluator(EvalSemantic):
380
380
  rank: int,
381
381
  env: dict[str, Any],
382
382
  comm: ICommunicator,
383
- runtime: BackendRuntime,
383
+ runtime: RuntimeContext,
384
384
  ) -> None:
385
385
  super().__init__(rank, env, comm, runtime)
386
386
 
@@ -501,7 +501,7 @@ def create_evaluator(
501
501
  rank: int,
502
502
  env: dict[str, Any],
503
503
  comm: ICommunicator,
504
- runtime: BackendRuntime,
504
+ runtime: RuntimeContext,
505
505
  kind: str | None = "iterative",
506
506
  ) -> IEvaluator:
507
507
  """Factory to create an evaluator engine.
mplang/frontend/base.py CHANGED
@@ -129,7 +129,7 @@ class FeModule(ABC):
129
129
  - You need compilation/stateful behavior/dynamic routing, multiple PFunctions, or complex capture flows.
130
130
 
131
131
  Tips:
132
- - Keep routing information in PFunction.fn_type (e.g., "builtin.read", "sql[duckdb]", "mlir.stablehlo").
132
+ - Keep routing information in PFunction.fn_type (e.g., "builtin.read", "sql.run", "mlir.stablehlo").
133
133
  - Avoid backend-specific logic in kernels; only validate and shape types.
134
134
  - Prefer keyword-only attributes in typed_op kernels for clarity (def op(x: MPObject, *, attr: int)).
135
135
  """
@@ -57,8 +57,9 @@ def ibis2sql(
57
57
  outs_info = [_convert(expr.schema())]
58
58
 
59
59
  sql = ibis.to_sql(expr, dialect="duckdb")
60
+ # Emit generic sql.run op; runtime maps to backend-specific kernel.
60
61
  pfn = PFunction(
61
- fn_type="sql[duckdb]",
62
+ fn_type="sql.run",
62
63
  fn_name=fn_name,
63
64
  fn_text=sql,
64
65
  ins_info=tuple(ins_info),
mplang/frontend/spu.py CHANGED
@@ -94,9 +94,10 @@ def _compile_jax(
94
94
  *args: Any,
95
95
  **kwargs: Any,
96
96
  ) -> tuple[PFunction, list[MPObject], PyTreeDef]:
97
- """Compile a JAX function into SPU pphlo MLIR representation."""
97
+ """Compile a JAX function into SPU pphlo MLIR and wrap as PFunction.
98
98
 
99
- """Compile a JAX function into SPU pphlo MLIR representation."""
99
+ Resulting PFunction uses fn_type 'spu.run_pphlo'.
100
+ """
100
101
 
101
102
  def is_variable(arg: Any) -> bool:
102
103
  return isinstance(arg, MPObject)
@@ -132,7 +133,7 @@ def _compile_jax(
132
133
  executable_code = executable_code.decode("utf-8")
133
134
 
134
135
  pfunc = PFunction(
135
- fn_type="mlir.pphlo",
136
+ fn_type="spu.run_pphlo",
136
137
  ins_info=tuple(TensorType.from_obj(x) for x in in_vars),
137
138
  outs_info=tuple(output_tensor_infos),
138
139
  fn_name=get_fn_name(fn),
@@ -26,17 +26,7 @@ from urllib.parse import urlparse
26
26
  import cloudpickle as pickle
27
27
  import spu.libspu as libspu
28
28
 
29
- # Import backends (side-effect: kernel registration)
30
- from mplang.backend import ( # noqa: F401
31
- builtin,
32
- crypto,
33
- phe,
34
- spu, # registers flat SPU kernels
35
- sql_duckdb,
36
- stablehlo,
37
- tee,
38
- )
39
- from mplang.backend.base import BackendRuntime, create_runtime
29
+ from mplang.backend.context import RuntimeContext
40
30
  from mplang.backend.spu import PFunction # type: ignore
41
31
  from mplang.core.expr.ast import Expr
42
32
  from mplang.core.expr.evaluator import IEvaluator, create_evaluator
@@ -98,9 +88,6 @@ class Session:
98
88
  # Global session storage
99
89
  _sessions: dict[str, Session] = {}
100
90
 
101
- # Service-level global symbol table (process-local for this server)
102
- _global_symbols: dict[str, Symbol] = {}
103
-
104
91
 
105
92
  # Session Management
106
93
  def create_session(
@@ -147,6 +134,43 @@ def delete_session(name: str) -> bool:
147
134
  return False
148
135
 
149
136
 
137
+ # Global symbol management (process-wide, not per-session)
138
+ _global_symbols: dict[str, Symbol] = {}
139
+
140
+
141
+ def create_global_symbol(name: str, mptype: dict[str, Any], data_b64: str) -> Symbol:
142
+ """Create or replace a global symbol.
143
+
144
+ Args:
145
+ name: Symbol identifier
146
+ mptype: Metadata dict (shape/dtype, etc.)
147
+ data_b64: Base64-encoded pickled data
148
+ """
149
+ try:
150
+ raw = base64.b64decode(data_b64)
151
+ data = pickle.loads(raw)
152
+ except Exception as e: # pragma: no cover - defensive
153
+ raise InvalidRequestError(f"Failed to decode symbol payload: {e}") from e
154
+ sym = Symbol(name=name, mptype=mptype, data=data)
155
+ _global_symbols[name] = sym
156
+ return sym
157
+
158
+
159
+ def get_global_symbol(name: str) -> Symbol:
160
+ sym = _global_symbols.get(name)
161
+ if sym is None:
162
+ raise ResourceNotFound(f"Global symbol '{name}' not found")
163
+ return sym
164
+
165
+
166
+ def delete_global_symbol(name: str) -> bool:
167
+ return _global_symbols.pop(name, None) is not None
168
+
169
+
170
+ def list_global_symbols() -> list[str]: # pragma: no cover - trivial
171
+ return sorted(_global_symbols.keys())
172
+
173
+
150
174
  # Computation Management
151
175
  def create_computation(
152
176
  session_name: str, computation_name: str, expr: Expr
@@ -225,15 +249,7 @@ def execute_computation(
225
249
 
226
250
  # Build evaluator
227
251
  # Explicit per-rank backend runtime (deglobalized)
228
- runtime = create_runtime(rank, session.communicator.world_size)
229
- # Inject global symbol storage into backend runtime state so that
230
- # symbols:// provider can access it during builtin.read/write.
231
- if isinstance(runtime, BackendRuntime):
232
- pocket = runtime.state.setdefault("resource.providers", {})
233
- if "symbols" not in pocket:
234
- pocket["symbols"] = _global_symbols
235
- else:
236
- raise RuntimeError("resource.providers.symbols already exists")
252
+ runtime = RuntimeContext(rank=rank, world_size=session.communicator.world_size)
237
253
  evaluator: IEvaluator = create_evaluator(
238
254
  rank=rank, env=bindings, comm=session.communicator, runtime=runtime
239
255
  )
@@ -285,45 +301,6 @@ def execute_computation(
285
301
  session.symbols[name] = Symbol(name=name, mptype={}, data=val)
286
302
 
287
303
 
288
- # Global symbol CRUD (service-level)
289
- def create_global_symbol(name: str, mptype: Any, data: str) -> Symbol:
290
- """Create or update a global symbol in the service-level table.
291
-
292
- WARNING: Uses Python pickle for arbitrary object deserialization. Deploy
293
- only in trusted environments. Future work may replace this with a
294
- restricted / structured serialization.
295
-
296
- The `data` argument is a base64-encoded pickled Python object. Minimal
297
- validation of `mptype` is performed for tensor metadata (shape/dtype)
298
- when present to catch obvious mismatches.
299
- """
300
- try:
301
- data_bytes = base64.b64decode(data)
302
- obj = pickle.loads(data_bytes)
303
- except Exception as e:
304
- raise InvalidRequestError(f"Invalid global symbol data encoding: {e!s}") from e
305
-
306
- sym = Symbol(name, mptype, obj)
307
- _global_symbols[name] = sym
308
- return sym
309
-
310
-
311
- def get_global_symbol(name: str) -> Symbol | None:
312
- return _global_symbols.get(name)
313
-
314
-
315
- def list_global_symbols() -> list[str]:
316
- return list(_global_symbols.keys())
317
-
318
-
319
- def delete_global_symbol(name: str) -> bool:
320
- if name in _global_symbols:
321
- del _global_symbols[name]
322
- logging.info(f"Global symbol {name} deleted")
323
- return True
324
- return False
325
-
326
-
327
304
  # Symbol Management
328
305
  def create_symbol(session_name: str, name: str, mptype: Any, data: Any) -> Symbol:
329
306
  """Create a symbol in a session's symbol table.
@@ -24,17 +24,10 @@ from typing import Any, cast
24
24
 
25
25
  import spu.libspu as libspu
26
26
 
27
- # Import flat backends for kernel registration side-effects
28
- from mplang.backend import (
29
- builtin, # noqa: F401
30
- crypto, # noqa: F401
31
- phe, # noqa: F401
32
- spu, # noqa: F401 # ensure SPU kernels (spu.seed_env etc.) registered
33
- sql_duckdb, # noqa: F401
34
- stablehlo, # noqa: F401
35
- tee, # noqa: F401
36
- )
37
- from mplang.backend.base import create_runtime # explicit per-rank backend runtime
27
+ # New explicit binding model: we only need RuntimeContext which ensures
28
+ # bindings via bind_all_ops() on creation; per-module side-effect imports
29
+ # are no longer required here.
30
+ from mplang.backend.context import RuntimeContext
38
31
  from mplang.core.cluster import ClusterSpec
39
32
  from mplang.core.comm import CollectiveMixin, CommunicatorBase
40
33
  from mplang.core.expr.ast import Expr
@@ -152,7 +145,7 @@ class Simulator(InterpContext):
152
145
 
153
146
  self._evaluators: list[IEvaluator] = []
154
147
  for rank in range(self.world_size()):
155
- runtime = create_runtime(rank, self.world_size())
148
+ runtime = RuntimeContext(rank=rank, world_size=self.world_size())
156
149
  ev = create_evaluator(
157
150
  rank,
158
151
  {}, # the global environment for this rank
@@ -224,7 +217,7 @@ class Simulator(InterpContext):
224
217
  # Build per-rank evaluators with the per-party environment
225
218
  pts_evaluators: list[IEvaluator] = []
226
219
  for rank in range(self.world_size()):
227
- runtime = create_runtime(rank, self.world_size())
220
+ runtime = RuntimeContext(rank=rank, world_size=self.world_size())
228
221
  ev = create_evaluator(
229
222
  rank,
230
223
  pts_env[rank],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mplang-nightly
3
- Version: 0.1.dev143
3
+ Version: 0.1.dev144
4
4
  Summary: Multi-Party Programming Language
5
5
  Author-email: SecretFlow Team <secretflow-contact@service.alipay.com>
6
6
  License: Apache License
@@ -3,13 +3,14 @@ mplang/api.py,sha256=ssmv0_CyZPFORhOUJ84Jo6NwRJSK7_Ono3n7ZjEg4sA,3058
3
3
  mplang/device.py,sha256=Iz_YFKkrbTFKtTQdGqkQZfc0NQH9dIxXP7-fUkIQOa4,12568
4
4
  mplang/analysis/__init__.py,sha256=CTHFvRsi-nFngojqjn08UaR3RY9i7CJ7T2UdR95kCrk,1056
5
5
  mplang/analysis/diagram.py,sha256=ffwgD12gL1_KH1uJ_EYkjmIlDrfxYJJkWj-wHl09_Xk,19520
6
- mplang/backend/__init__.py,sha256=Pn1MGW7FZ8ZQWcx_r2Io4Q1JbrMINCpefQt7yCNq_dc,741
7
- mplang/backend/base.py,sha256=zaofB1MPC9Af8FS-rx7q6utFxiu9Mppr2gWaKFZ70Os,9863
6
+ mplang/backend/__init__.py,sha256=2WE4cmW96Xkzyq2yRRYNww4kZ5o6u6NbPV0BxqZG698,581
7
+ mplang/backend/base.py,sha256=Rp4Ze1aF5dypdMOskXnQWE-rM5nc4vkdDogEoxQp7FU,5712
8
8
  mplang/backend/builtin.py,sha256=Mk1uUO2Vpw3meqZ0B7B0hG-wndea6cmFv2Uk1vM_uTg,7052
9
+ mplang/backend/context.py,sha256=e22JzsHydi1DLkjzOEYwGBaAoVHyf8aEFYH_SuN7oR4,9351
9
10
  mplang/backend/crypto.py,sha256=H_s5HI7lUP7g0xz-a9qMbSn6dhJStUilKbn3-7SIh0I,3812
10
11
  mplang/backend/phe.py,sha256=uNqmrbDAbd97TWS_O6D5sopastHy6J20R7knFE4M4uc,65247
11
- mplang/backend/spu.py,sha256=i6Qqgeg-Anwpb5xX5Uz8GdmTWNkRy_pjp-xptIvlxl4,9273
12
- mplang/backend/sql_duckdb.py,sha256=WFEiJk3t68DMpjV9xZz5bJj3v8UMx4X5QWleLIE4Aq0,1499
12
+ mplang/backend/spu.py,sha256=QT1q5uv-5P_nBGtTvtA_yI2h3h3zIqNSnvzGT7Shua4,9307
13
+ mplang/backend/sql_duckdb.py,sha256=U_KzEUinxrBRDoUz2Vh597-N4I3hPOBT0RT3tX-ZqKE,1502
13
14
  mplang/backend/stablehlo.py,sha256=GOxy-qgOxyEdtBkt6LASKzaZnPewZhvHYSPOgFFXgIM,2612
14
15
  mplang/backend/tee.py,sha256=6kc7qTe8nWc3pr6iYtozEGLO8Umg-UBQLDiz6p3pdVg,1918
15
16
  mplang/core/__init__.py,sha256=lWxlEKfRwX7FNDzgyKZ1fiDMaCiqkyg0j5mKlZD_v7g,2244
@@ -29,20 +30,20 @@ mplang/core/tensor.py,sha256=86u6DogSZMoL0w5XjtTmQm2PhA_VjwybN1b6U4Zzphg,2361
29
30
  mplang/core/tracer.py,sha256=dVMfUeCMmPz4o6tLXewGMW1Kpy5gpZORvr9w4MhwDtM,14288
30
31
  mplang/core/expr/__init__.py,sha256=qwiSTUOcanFJLyK8HZ13_L1ZDrybqpPXIlTHAyeumE8,1988
31
32
  mplang/core/expr/ast.py,sha256=KE46KTtlH9RA2V_EzWVKCKolsycgTmt7SotUrOc8Qxs,20923
32
- mplang/core/expr/evaluator.py,sha256=0ZwDgRyzHh1zxmb-9XW85toVKpk2mk0_t-hY_10U_Q8,20786
33
+ mplang/core/expr/evaluator.py,sha256=3rtfHVajmCUtkanzgiCWkepRRiTKehPcDF2i5EbcitI,20789
33
34
  mplang/core/expr/printer.py,sha256=VblKGnO0OUfzH7EBkszwRNxQUB8QyyC7BlJWJEUv9so,9546
34
35
  mplang/core/expr/transformer.py,sha256=TyL-8FjrVvDq_C9X7kAuKkiqt2XdZM-okjzVQj0A33s,4893
35
36
  mplang/core/expr/utils.py,sha256=VDTJ_-CsdHtVy9wDaGa7XdFxQ7o5lYYaeqcgsAhkbNI,2625
36
37
  mplang/core/expr/visitor.py,sha256=2Ge-I5N-wH8VVXy8d2WyNaEv8x6seiRx9peyH9S2BYU,2044
37
38
  mplang/core/expr/walk.py,sha256=lXkGJEEuvKGDqQihbxXPxfz2RfR1Q1zYUlt11iooQW0,11889
38
39
  mplang/frontend/__init__.py,sha256=3ZBFX_acM96tZ2mtJaxJm150n1cf0LnnCRmkrAc4uBw,1463
39
- mplang/frontend/base.py,sha256=I-Hhh5o6GVqBA1YySl9Nk3zkbMoVrQqLMRyX2JypsPI,18268
40
+ mplang/frontend/base.py,sha256=rGtfBejcDh9mTRxOdJK5VUlG5vYiVJSir8X72X0Huvc,18264
40
41
  mplang/frontend/builtin.py,sha256=8qrlbe_SSy6QTXTnMG6_ADB8jSklVZGFBrkoR-p02FE,9368
41
42
  mplang/frontend/crypto.py,sha256=Nf8zT4Eko7MIs4R2tgZecKVd7d6Hvd_CGGmANhs3Ghs,3651
42
- mplang/frontend/ibis_cc.py,sha256=01joUdFS_Ja9--PkezBhEcW_a9mkDrLgOhu5320s_bQ,4167
43
+ mplang/frontend/ibis_cc.py,sha256=CTTbPPZ9hFnHuFDDIfgJHie1EdNnHmi5Ha1KsX0iYh8,4235
43
44
  mplang/frontend/jax_cc.py,sha256=ssP6rCvyWQ5VAr80-7z9QZUE2mWXyozJCGpq1dYQYY8,6374
44
45
  mplang/frontend/phe.py,sha256=tDsCvStjVJ1Fs07yF3idkFnugUCA1zdFApPx7Uuulik,6795
45
- mplang/frontend/spu.py,sha256=yvnXH8HU55t7j_jaTpxa3Nbh0SqXufVDyZBTYCsRTK4,4994
46
+ mplang/frontend/spu.py,sha256=7G6DaEfC5APSDhfeWSISTG_8tEcVbWth3XmjL8QUrVA,4994
46
47
  mplang/frontend/sql.py,sha256=DFdvjEPQX28VCRgUMeHYR0rwwOaoCH15bpvvlclLtHA,1999
47
48
  mplang/frontend/tee.py,sha256=EigmlbYDGvXkZCMHSYRAiOboSl9TG0ewoudbgl3_V6M,1393
48
49
  mplang/protos/v1alpha1/mpir_pb2.py,sha256=Bros37t-4LMJbuUYVSM65rImUYTtZDhNTIADGbZCKp0,7522
@@ -57,9 +58,9 @@ mplang/runtime/driver.py,sha256=Ok1jY301ctN1_KTb4jwSxOdB0lI_xhx9AwhtEGJ-VLQ,1130
57
58
  mplang/runtime/exceptions.py,sha256=c18U0xK20dRmgZo0ogTf5vXlkix9y3VAFuzkHxaXPEk,981
58
59
  mplang/runtime/http_api.md,sha256=-re1DhEqMplAkv_wnqEU-PSs8tTzf4-Ml0Gq0f3Go6s,4883
59
60
  mplang/runtime/link_comm.py,sha256=uNqTCGZVwWeuHAb7yXXQf0DUsMXLa8leHCkrcZdzYMU,4559
60
- mplang/runtime/resource.py,sha256=BkzpAjRrkjS-5FPawHHcVzxEE_htpMtS4JJCvcLrnU0,12564
61
+ mplang/runtime/resource.py,sha256=-B9kSM7xhocc6mpXHmV9xTdpVR2duiUCepJKS7QuLqA,11688
61
62
  mplang/runtime/server.py,sha256=gTPqAux1EdefaBFnserYIXamoi7pbEsQrFX6cXbOjik,14716
62
- mplang/runtime/simulation.py,sha256=kj-RtvvypISO2xyYQGtm-N8yavnkVpD33KSLpvL-Vms,11107
63
+ mplang/runtime/simulation.py,sha256=kuFXWuJLGcmy4OvLCBby4K5QbXaQZmKSb4qrCJ2stBY,10957
63
64
  mplang/simp/__init__.py,sha256=DmSMcKvHVXWS2pYsuHazEmwOWWpZeKOJQsNU6VxC10U,11614
64
65
  mplang/simp/mpi.py,sha256=Wv_Q16TQ3rdLam6OzqXiefIGSMmagGkso09ycyOkHEs,4774
65
66
  mplang/simp/random.py,sha256=7PVgWNL1j7Sf3MqT5PRiWplUu-0dyhF3Ub566iqX86M,3898
@@ -69,8 +70,8 @@ mplang/utils/crypto.py,sha256=rvPomBFtznRHc3RPi6Aip9lsU8zW2oxBqGv1K3vn7Rs,1052
69
70
  mplang/utils/func_utils.py,sha256=vCJcZmu0bEbqhOQKdpttV2_MBllIcPSN0b8U4WjNGGo,5164
70
71
  mplang/utils/spu_utils.py,sha256=S3L9RBkBe2AvSuMSQQ12cBY5Y1NPthubvErSX_7nj1A,4158
71
72
  mplang/utils/table_utils.py,sha256=aC-IZOKkSmFkpr3NZchLM0Wt0GOn-rg_xHBHREWBwAU,2202
72
- mplang_nightly-0.1.dev143.dist-info/METADATA,sha256=BvANiZEEU0N7uAY_k8Yd4p1rZKGPpdbrM5giBinXzdA,16547
73
- mplang_nightly-0.1.dev143.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
74
- mplang_nightly-0.1.dev143.dist-info/entry_points.txt,sha256=mG1oJT-GAjQR834a62_QIWb7litzWPPyVnwFqm-rWuY,55
75
- mplang_nightly-0.1.dev143.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
76
- mplang_nightly-0.1.dev143.dist-info/RECORD,,
73
+ mplang_nightly-0.1.dev144.dist-info/METADATA,sha256=i6ef5f8u6W_vm_UfZcAPsekVJ0j4a9wNYCdVDKJRBgw,16547
74
+ mplang_nightly-0.1.dev144.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
75
+ mplang_nightly-0.1.dev144.dist-info/entry_points.txt,sha256=mG1oJT-GAjQR834a62_QIWb7litzWPPyVnwFqm-rWuY,55
76
+ mplang_nightly-0.1.dev144.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
77
+ mplang_nightly-0.1.dev144.dist-info/RECORD,,