1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
//! Stack operations for the VM executor (ADR-006 §2.7.7 / Q9 — kinded stack).
//!
//! Handles basic stack manipulation: PushConst, PushNull, Pop, Dup, Swap,
//! plus the legacy `PromoteToOwned` / `PromoteToShared` opcodes.
//!
//! Wave 6: every push/pop now threads through the kinded API
//! (`push_kinded(bits, kind)` / `pop_kinded()`). Kind is sourced from the
//! constant being pushed (compile-time-known per Constant variant). The
//! Box-vs-Arc heap promotion machinery (`PromoteToOwned`/`PromoteToShared`)
//! becomes a no-op: the kinded model carries `Arc<T>` directly per
//! `KindedSlot::from_*` constructors — there is no Box-owned encoding.
use crate::{
bytecode::{Instruction, OpCode, Operand},
executor::vm_impl::stack::{clone_with_kind, drop_with_kind},
executor::VirtualMachine,
};
use shape_value::{NativeKind, VMError, heap_value::{HeapKind, TemporalData}};
use std::sync::Arc;
impl VirtualMachine {
#[inline(always)]
pub(in crate::executor) fn exec_stack_ops(
&mut self,
instruction: &Instruction,
) -> Result<(), VMError> {
use OpCode::*;
match instruction.opcode {
PushConst => self.op_push_const(instruction)?,
// R5b-2-bool-null-sentinel-cluster (ADR-006 §2.7 + §2.7.5 +
// §2.7.7/Q9, 2026-05-19): pre-disposition `PushNull` pushed
// `(0u64, NativeKind::Bool)` as the §2.7 null sentinel,
// colliding with legitimate `false` bool slots (both encoded
// as bits=0). Caused VM-only divergence per W14.2-G6
// SURFACE-G6-BOOL-NULL + SURFACE-G6-LET-ONLY-BODY (implicit-
// return null at compiler/functions.rs:1633 materialized as
// `Bool(false)`). Post-disposition: kind IS the
// discriminator per §2.7.7/Q9 — push `NativeKind::Null`.
PushNull => self.push_kinded(0u64, NativeKind::Null)?,
Pop => {
let (bits, kind) = self.pop_kinded()?;
drop_with_kind(bits, kind);
}
Dup => {
// WB2.4 retain-on-read: `Dup` produces an independent
// owning share of the top-of-stack. Bump the heap refcount
// via `clone_with_kind` so both stack slots own a share.
let index = self.sp.checked_sub(1).ok_or(VMError::StackUnderflow)?;
let (bits, kind) = self.stack_read_kinded_raw(index);
clone_with_kind(bits, kind);
self.push_kinded(bits, kind)?;
}
Swap => {
let (b_bits, b_kind) = self.pop_kinded()?;
let (a_bits, a_kind) = self.pop_kinded()?;
self.push_kinded(b_bits, b_kind)?;
self.push_kinded(a_bits, a_kind)?;
}
// ADR-006: heap values are always Arc-backed via `KindedSlot::from_*`
// constructors. The pre-Wave-6 Box-owned encoding (HEAP_OWNED_BIT)
// is gone; PromoteToOwned / PromoteToShared collapse to no-ops.
// The opcodes are preserved for bytecode compatibility (existing
// FunctionBlobs reference them); the runtime semantics are now
// "ensure top-of-stack is Arc-backed" — already true by
// construction.
PromoteToOwned | ReturnOwned | PromoteToShared => {
// No-op: the kinded model never produces Box-backed slots.
}
_ => unreachable!(
"exec_stack_ops called with non-stack opcode: {:?}",
instruction.opcode
),
}
Ok(())
}
pub(in crate::executor) fn op_push_const(
&mut self,
instruction: &Instruction,
) -> Result<(), VMError> {
if let Some(Operand::Const(idx)) = instruction.operand {
let constant = self
.program
.constants
.get(idx as usize)
.ok_or(VMError::InvalidOperand)?;
// Wave 6: the kind for each Constant variant is compile-time
// known. Push raw bits + the corresponding NativeKind into the
// parallel kinds track.
match constant {
crate::bytecode::Constant::Number(n) => {
let bits = if n.is_nan() {
f64::NAN.to_bits()
} else {
n.to_bits()
};
return self.push_kinded(bits, NativeKind::Float64);
}
crate::bytecode::Constant::Int(i) => {
return self.push_kinded(*i as u64, NativeKind::Int64);
}
crate::bytecode::Constant::UInt(u) => {
return self.push_kinded(*u, NativeKind::UInt64);
}
crate::bytecode::Constant::Bool(b) => {
return self.push_kinded(*b as u64, NativeKind::Bool);
}
// R5b-2-bool-null-sentinel-cluster (ADR-006 §2.7 + §2.7.5
// + §2.7.7/Q9, 2026-05-19): pre-disposition Null and Unit
// constants pushed `(0u64, NativeKind::Bool)` as the §2.7
// sentinel, colliding with legitimate `false` bool slots.
// Post-disposition uses `NativeKind::Null` — kind is the
// discriminator. Drop is no-op (Null is a non-parametric
// absence-of-value sentinel with no Arc<T> payload).
crate::bytecode::Constant::Null => {
return self.push_kinded(0u64, NativeKind::Null);
}
// Unit: same shape as Null (no payload).
crate::bytecode::Constant::Unit => {
return self.push_kinded(0u64, NativeKind::Null);
}
crate::bytecode::Constant::Function(id) => {
// Function ID is an inline u16 stored in the lower bits.
return self.push_kinded(*id as u64, NativeKind::UInt64);
}
_ => {}
}
// Heap-bearing constants: construct the matching Arc<T> and
// push raw pointer bits with the per-kind discriminator.
match constant {
crate::bytecode::Constant::String(s) => {
let arc: Arc<String> = Arc::new(s.clone());
let bits = Arc::into_raw(arc) as u64;
return self.push_kinded(bits, NativeKind::String);
}
crate::bytecode::Constant::Char(c) => {
// Char: inline 4-byte scalar (UTF-32 codepoint), no
// Arc<T>. ADR-006 §2.7.5 producer-side stamp — the
// scalar `NativeKind::Char` kind is canonical; the prior
// `Ptr(HeapKind::Char)` label mislabeled the codepoint as
// a heap pointer (consumers without a Char arm would
// deref the codepoint bits → misaligned-pointer abort).
return self.push_kinded(*c as u64, NativeKind::Char);
}
crate::bytecode::Constant::Decimal(d) => {
let arc: Arc<rust_decimal::Decimal> = Arc::new(*d);
let bits = Arc::into_raw(arc) as u64;
return self
.push_kinded(bits, NativeKind::Ptr(HeapKind::Decimal));
}
// C1-temporal-lowering (Phase 2d Wave 2): Duration literals
// (e.g. `3d`, `10s`) lower to `TemporalData::TimeSpan` via
// the existing `ast_duration_to_chrono` helper. The slot's
// bits are `Arc::into_raw::<TemporalData>` and the kind is
// `NativeKind::Ptr(HeapKind::Temporal)` per ADR-006 §2.7.4
// (Temporal carrier dispatch). The TIMESPAN_METHODS PHF in
// `objects/datetime_methods.rs` already recovers
// `&TemporalData` via `recv_temporal` (§2.7.6/Q8 heap-value
// match), so addition / subtraction / printing flow through
// the existing dispatch surface.
crate::bytecode::Constant::Duration(d) => {
let chrono_dur =
crate::executor::builtins::datetime_builtins::ast_duration_to_chrono(d);
let arc: Arc<TemporalData> =
Arc::new(TemporalData::TimeSpan(chrono_dur));
let bits = Arc::into_raw(arc) as u64;
return self
.push_kinded(bits, NativeKind::Ptr(HeapKind::Temporal));
}
// C1-temporal-lowering: DateTimeExpr literals (e.g.
// `@"2026-01-01"`, `@now`, `@today`) evaluate to
// `TemporalData::DateTime` at execution time via the
// pure-AST `eval_datetime_expr_recursive` helper in
// `executor/window_join.rs:401` (no VM state consumed; uses
// wall-clock-time when the AST asks for `@now`/`@today`).
// The companion `BuiltinCall(EvalDateTimeExpr)` emitted by
// `compiler/expressions/temporal.rs:42` becomes an identity
// passthrough — the value is already produced here.
// ADR-006 §2.7.4 (Temporal carrier dispatch).
crate::bytecode::Constant::DateTimeExpr(expr) => {
let expr_clone = expr.clone();
let dt = self.eval_datetime_expr_recursive(&expr_clone)?;
let arc: Arc<TemporalData> =
Arc::new(TemporalData::DateTime(dt));
let bits = Arc::into_raw(arc) as u64;
return self
.push_kinded(bits, NativeKind::Ptr(HeapKind::Temporal));
}
// R8 W3 W17-typed-module-exports-followup-constant-pool
// (ADR-006 §2.7.4 / §2.7.7 / Q9, 2026-05-24): kinded
// constant carrier — host-injected RowView / DataTable /
// TypedTable / etc. values. The constant holds one
// `Arc::into_raw::<T>` share for heap-bearing kinds;
// `clone_with_kind` bumps the refcount so the stack
// gets its own share and the constant retains its own
// (subsequent loads see the same payload). Inline
// scalar kinds no-op on `clone_with_kind`. Mirror of
// `Constant::Decimal` shape above except the kind is
// sourced from the constant (not statically known by
// the variant).
crate::bytecode::Constant::Value(kc) => {
let bits = kc.bits();
let kind = kc.kind();
crate::executor::vm_impl::stack::clone_with_kind(bits, kind);
return self.push_kinded(bits, kind);
}
_ => {}
}
// Remaining complex constants (Timeframe, TimeReference,
// DataDateTimeRef, TypeAnnotation): these are deferred
// to a follow-up wave that aligns the constant table with the
// kinded heap encoding. The temporal-carrier arms (Duration /
// DateTimeExpr) and the kinded `Value(KindedConstant)` arm are
// handled above; pure data-reference flavours belong to the
// data-reference / pipeline subsystems whose runtime entry
// points are themselves SURFACE per their own sub-clusters
// (W8-WJ for window_join, D-data-refs cascade).
return Err(VMError::RuntimeError(format!(
"unsupported constant variant in PushConst (Wave 6 follow-up): {:?}",
std::mem::discriminant(constant)
)));
}
Err(VMError::InvalidOperand)
}
}