Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ backported fixes.
- Every failure is fail-stop through `error()`; the public surface
raises no exceptions and is `-fno-exceptions`-compatible (enforced by
`test_no_exceptions`).
- Runtime-width integers are now explicit `DynamicUInt_T<Ctx>` /
`DynamicInt_T<Ctx>` types. Fixed `UInt_T<Ctx,N>` / `Int_T<Ctx,N>` require
`N > 0`; migrate former `<Ctx,0>` and `<Ctx,runtime_width>` uses to the
dynamic names. `UInt_T::mod_exp` was removed.
- IR diagnostics now separate a streamed gate trace
(`DigestCtx::value()` / `digest_gate_stream`) from the versioned full-program
fingerprint (`digest_program`). Code that read `DigestCtx::digest` must call
Expand Down
6 changes: 3 additions & 3 deletions docs/EMP_TRANSLATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ when you operate on already-live values, `Ctx` is the concrete context type
### 2.1. Width is a type parameter

For the fixed-width forms used throughout this guide, the width is part of
the type. (In-circuit runtime-width forms `UInt_T<Ctx,0>` / `Int_T<Ctx,0>`
exist — see [docs/frontend.md](frontend.md) — but translated code should
the type. (`DynamicUInt_T<Ctx>` / `DynamicInt_T<Ctx>` exist for widths known
only at runtime — see [docs/frontend.md](frontend.md) — but translated code should
commit to fixed widths.) Make a public constant with `T::constant(ctx, v)`
(or `a.constant(v)` from an existing value of the same family/context):

Expand Down Expand Up @@ -619,7 +619,7 @@ UInt_T<Ctx,N> // unsigned N-bit (clear: uint64_t, N
<< >> by UInt_T (secret amount) // barrel shifter, costs ANDs
slice<Lo,Hi>() extract<B,W>() concat(hi) // compile-time width changes (free)
zext<M>() trunc<M>()
popcount<R>() hamming_weight() leading_zeros() mod_exp(p, q)
popcount<R>() hamming_weight() leading_zeros()
as_signed() // reinterpret wires (free)
operator[i] -> Bit_T (i in [0,N))
constant(ctx, uint64_t)
Expand Down
38 changes: 20 additions & 18 deletions docs/circuits.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
Conventions for the circuit value types under `emp-tool/circuits/`.

The circuit value layer is the **context-bound typed values**: `Bit_T<Ctx>`,
`UInt_T<Ctx,N>`, `Int_T<Ctx,N>`, `Float_T<Ctx,W>`, and `BitVec_T<Ctx,N>`,
`UInt_T<Ctx,N>`, `Int_T<Ctx,N>`, `Float_T<Ctx,W>`, and `BitVec_T<Ctx,N>`, plus
`DynamicUInt_T<Ctx>` / `DynamicInt_T<Ctx>` when width is known only at runtime,
templated on a `BooleanContext` (`ir/context/concept.h`, re-exported by the
`ir/context/context.h` umbrella). Static dispatch, no global backend; each value
carries its own `Ctx*` and issues value-return gates on it.
Expand All @@ -18,7 +19,7 @@ Each value type lives in its own header — `circuits/{bit,bitvec,unsigned_int,
signed_int,float}.h` — over the shared arithmetic in `circuits/numeric_kernels.h`.
Two umbrellas gather them:

- `circuits/typed.h` — the value types (`#include` it to get all five).
- `circuits/typed.h` — the fixed value families and dynamic integer siblings.
- `circuits/circuits.h` — the whole circuits layer: values + `value_traits.h` +
numeric kernels + sorting (`sort.h`) + the in-circuit crypto
(`circuits/crypto/crypto.h` = aes128 / sha256 / keccak) + the compile/run frontend.
Expand All @@ -29,9 +30,10 @@ primitives, read [EMP_TRANSLATION.md](EMP_TRANSLATION.md).

## Context-bound values

Each value is a small struct templated on a `BooleanContext` `Ctx`. It holds its
wires inline (`Wire w;` in `Bit_T`, `std::array<Wire,N> w;` in the rest — a
`std::vector<Wire>` for the runtime-width `UInt_T<Ctx,0>` / `Int_T<Ctx,0>`) plus a
Each value is a small struct templated on a `BooleanContext` `Ctx`. Fixed values
hold wires inline (`Wire w;` in `Bit_T`, `std::array<Wire,N> w;` in the other
fixed families); `DynamicUInt_T<Ctx>` / `DynamicInt_T<Ctx>` use a private
`std::vector<Wire>`. Each also holds a
private `Ctx*` (reached via `context()`); operators issue value-return gates on the
context. No inheritance, no marker base, no global backend.

Expand Down Expand Up @@ -87,7 +89,7 @@ party that does not; a plaintext `ClearSession` always populates it.
- `UInt_T<N>` — `+ - * / %`, comparisons, `& | ^ ~`, public-amount shifts/rotates
(`<<`/`>>`/`rotl`/`rotr` by `int`), secret-amount shifts (`<<`/`>>` by a
`UInt_T` — a barrel shifter), `slice`/`extract`/`concat`/`zext`/`trunc`,
`hamming_weight`/`popcount<R>`, `leading_zeros`, `mod_exp`, `as_signed`.
`hamming_weight`/`popcount<R>`, `leading_zeros`, `as_signed`.
- `Int_T<N>` — two's-complement `+ - * / %` (truncating), `-`(negate), signed
comparisons, `& | ^ ~`, logical-left / arithmetic-right shifts, `sext`/`trunc`,
`as_unsigned`.
Expand All @@ -98,17 +100,17 @@ feeds through `input`/`reveal` and the frontend compiles). A wider fixed-width
`UInt_T` / `Int_T` (`N > 64`) has no 64-bit clear codec, so it is only a
`WireBundle` (frontend-compilable, usable as a circuit argument) but not
session-I/O-eligible — use `BitVec_T<Ctx,N>` for typed session I/O past 64 bits.
`N == 0` (the `runtime_width` sentinel) is
the *same* `UInt_T` / `Int_T` family with the width carried in the wire vector and
chosen at construction — `UInt_T<Ctx,0>(ctx, width)`, `UInt_T<Ctx,0>::constant(ctx,
width, v)`. It shares every operator above through the runtime-sized kernels; the
compile-time-width surface (`slice`/`extract`/`concat`/`zext`/`trunc`, secret-amount
barrel shifts, the clear codec, `popcount<R>`) is `requires (N > 0)` and so absent,
and it adds `resize(width)` plus fixed↔runtime conversion (`to_dynamic()` on a fixed
value, `to_fixed<M>()` on a runtime one). A runtime-width value is **not** a
`WireValue` — it is for data-driven in-circuit computation, not the frontend
`input`/`compile` boundary. (Width must be `>= 1`; wider-than-64 constants
zero-extend for `UInt_T` and sign-extend for `Int_T`.)
Runtime width uses the distinct `DynamicUInt_T<Ctx>` / `DynamicInt_T<Ctx>`
families, with width carried in a private wire vector and chosen at construction.
They share the same runtime-sized arithmetic kernels and add `resize(width)` plus
fixed↔dynamic conversion (`to_dynamic()` on a fixed value, `to_fixed<M>()` on a
dynamic one). A dynamic integer is not a `WireValue` or `WireBundle`, because it
has no static width, but it is `RuntimeWidthValue` and can use a session's runtime
`input`/`reveal` overloads. Bound values require width `>= 1`; wider-than-64
constants zero-extend for `DynamicUInt_T` and sign-extend for `DynamicInt_T`.
Default construction creates an unbound assignment target; moved-from values
may only be assigned or destroyed. `resize(width)` returns a new value, while
assignment replaces the target's width and wires.
- `Float_T<W>` — `+ - * / min max sqrt recip rsqrt fma`, comparisons / `is_nan` /
`is_inf` / `is_zero`, `abs`/negate/`copysign`/`select`. Arithmetic **replays**
the recorded `fp<W>_<op>.empbc` builtins through the context.
Expand All @@ -123,7 +125,7 @@ multiply, restoring division, `if_then_else`) live in namespace `emp::kernel` in
`Ctx::Wire` (no per-bit `Ctx*`). They are LSB-first and size-optimal: one AND per
full adder (an N-bit add is N−1 ANDs); unsigned `<` is the borrow-out of a
subtract (one AND/bit). The value-type operators forward to them, passing the width
as a runtime argument, so the fixed-width and runtime-width (`N == 0`) integers
as a runtime argument, so the fixed and dynamic integer families
share one kernel set. `Float_T` is the opposite — every nontrivial op is an
`.empbc` replay.

Expand Down
2 changes: 1 addition & 1 deletion docs/frontend.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ fixed width; `Bit_T` / `BitVec_T` / `Float_T` also model `WireValue` at each sup
while `UInt_T` / `Int_T` model `WireValue` only for width <= 64 (their clear
codecs ride 64-bit integers) — use `BitVec_T` for typed session I/O past 64 bits.
The runtime-width
forms `UInt_T<Ctx,0>` / `Int_T<Ctx,0>` (width chosen at construction)
types `DynamicUInt_T<Ctx>` / `DynamicInt_T<Ctx>` (width chosen at construction)
intentionally do **not** model `WireBundle` — they have no *static* `width()` —
so they cannot be a `compile` / `run` argument; convert to a fixed
`UInt_T<Ctx,N>` (`to_fixed<N>()`) first if a runtime-width result must enter the
Expand Down
4 changes: 2 additions & 2 deletions docs/numeric_semantics.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ To do a *logical* right-shift on an `Int_T`:

## Resize

- `resize(W)` is the runtime-width form (`UInt_T` / `Int_T` at
runtime width): zero-extends on `UInt_T`, sign-extends on `Int_T`,
- `resize(W)` is provided by `DynamicUInt_T` / `DynamicInt_T`:
it zero-extends the unsigned form, sign-extends the signed form,
truncates by dropping the high bits.
- For a fixed-width value use the compile-time views `zext<M>()` /
`trunc<M>()` on `UInt_T` and `sext<M>()` / `trunc<M>()` on `Int_T`.
Expand Down
4 changes: 2 additions & 2 deletions emp-tool/circuits/bitvec.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

namespace emp {

template <BooleanContext Ctx, int N>
template <BooleanContext Ctx, int N> requires (N >= 0)
class BitVec_T {
public:
using Wire = typename Ctx::Wire;
Expand All @@ -41,7 +41,7 @@ class BitVec_T {
Bit_T<Ctx> operator[](int i) const { return Bit_T<Ctx>(*ctx_, w[i]); }

// Reinterpret the same wires as an unsigned integer (zero gates).
UInt_T<Ctx, N> as_uint() const { return UInt_T<Ctx, N>::from_wires(*ctx_, w.data()); }
auto as_uint() const requires (N > 0) { return UInt_T<Ctx, N>::from_wires(*ctx_, w.data()); }

// --- bitwise ops / equality / select / logical shifts (public amount) ---
BitVec_T operator&(const BitVec_T& o) const { check_same_context(*this, o); BitVec_T r(*ctx_); for (int i = 0; i < N; ++i) r.w[i] = ctx_->and_gate(w[i], o.w[i]); return r; }
Expand Down
2 changes: 1 addition & 1 deletion emp-tool/circuits/circuits.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#define EMP_CIRCUITS_CIRCUITS_H__

// circuits — the concrete circuit value families (Bit_T / BitVec_T / UInt_T /
// Int_T / Float_T), their uniform metadata accessor (value_traits), the numeric
// Int_T / Float_T and dynamic integers), their uniform metadata accessor, the numeric
// kernels, sort, the in-circuit crypto primitives (AES-128 / SHA-256 / Keccak-f /
// SHA3-256), and the compile/run frontend. It builds on ir and runtime.

Expand Down
30 changes: 18 additions & 12 deletions emp-tool/circuits/numeric_kernels.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,15 +94,19 @@ inline typename Ctx::Wire mux(Ctx& c, typename Ctx::Wire sel,
return c.xor_gate(f, c.and_gate(sel, c.xor_gate(t, f)));
}

// unsigned a < b: the borrow-out of (a - b). sub_full produces that borrow in one
// AND per bit (the difference bits are discarded), the size-optimal shape.
// unsigned a < b: the borrow-out of (a - b), without materializing difference
// bits. One AND per input bit.
template <BooleanContext Ctx>
inline typename Ctx::Wire less_than(Ctx& c, const typename Ctx::Wire* a,
const typename Ctx::Wire* b, int N) {
using W = typename Ctx::Wire;
std::vector<W> diff(N);
W borrow;
sub_full<Ctx>(c, diff.data(), &borrow, a, b, nullptr, N);
W borrow = c.public_bit(false);
for (int i = 0; i < N; ++i) {
W bxa = c.xor_gate(a[i], b[i]);
W bxc = c.xor_gate(borrow, b[i]);
W t = c.and_gate(bxa, bxc);
borrow = c.xor_gate(borrow, t);
}
return borrow;
}

Expand All @@ -111,8 +115,9 @@ template <BooleanContext Ctx>
inline typename Ctx::Wire equal(Ctx& c, const typename Ctx::Wire* a,
const typename Ctx::Wire* b, int N) {
using W = typename Ctx::Wire;
W acc = c.public_bit(true);
for (int i = 0; i < N; ++i)
if (N == 0) return c.public_bit(true);
W acc = c.not_gate(c.xor_gate(a[0], b[0]));
for (int i = 1; i < N; ++i)
acc = c.and_gate(acc, c.not_gate(c.xor_gate(a[i], b[i]))); // acc & ~(a^b)
return acc;
}
Expand All @@ -136,9 +141,10 @@ template <BooleanContext Ctx>
inline void mul_full(Ctx& c, typename Ctx::Wire* dest,
const typename Ctx::Wire* op1, const typename Ctx::Wire* op2, int N) {
using W = typename Ctx::Wire;
std::vector<W> sum(N, c.public_bit(false));
std::vector<W> sum(N);
std::vector<W> tmp(N);
for (int i = 0; i < N; ++i) {
for (int k = 0; k < N; ++k) sum[k] = c.and_gate(op1[k], op2[0]);
for (int i = 1; i < N; ++i) {
for (int k = 0; k < N - i; ++k) tmp[k] = c.and_gate(op1[k], op2[i]);
add_full<Ctx>(c, sum.data() + i, nullptr, sum.data() + i, tmp.data(), nullptr, N - i);
}
Expand All @@ -151,7 +157,7 @@ template <BooleanContext Ctx>
inline void div_full(Ctx& c, typename Ctx::Wire* vquot, typename Ctx::Wire* vrem,
const typename Ctx::Wire* op1, const typename Ctx::Wire* op2, int N) {
using W = typename Ctx::Wire;
std::vector<W> overflow(N), tmp(N), rem(N), quot(N);
std::vector<W> overflow(N), tmp(N), rem(N);
W b;
for (int i = 0; i < N; ++i) rem[i] = op1[i];
overflow[0] = c.public_bit(false);
Expand All @@ -160,10 +166,10 @@ inline void div_full(Ctx& c, typename Ctx::Wire* vquot, typename Ctx::Wire* vrem
sub_full<Ctx>(c, tmp.data(), &b, rem.data() + i, op2, nullptr, N - i);
b = or_gate(c, b, overflow[i]);
if_then_else<Ctx>(c, rem.data() + i, rem.data() + i, tmp.data(), N - i, b);
quot[i] = c.not_gate(b);
overflow[i] = b;
}
if (vrem) for (int i = 0; i < N; ++i) vrem[i] = rem[i];
if (vquot) for (int i = 0; i < N; ++i) vquot[i] = quot[i];
if (vquot) for (int i = 0; i < N; ++i) vquot[i] = c.not_gate(overflow[i]);
}

} // namespace kernel
Expand Down
Loading