2022-08-07 22:40:29 +02:00
|
|
|
//! This module implements the instruction set running on the abstract
|
|
|
|
//! machine implemented by tvix.
|
|
|
|
|
2022-09-13 14:58:55 +02:00
|
|
|
use std::ops::{AddAssign, Sub};
|
|
|
|
|
2022-08-26 19:48:39 +02:00
|
|
|
/// Index of a constant in the current code chunk.
|
2022-08-26 19:46:43 +02:00
|
|
|
#[repr(transparent)]
|
2022-09-18 18:38:53 +02:00
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
2022-08-07 22:40:29 +02:00
|
|
|
pub struct ConstantIdx(pub usize);
|
|
|
|
|
2022-08-26 19:48:39 +02:00
|
|
|
/// Index of an instruction in the current code chunk.
|
2022-08-26 19:46:43 +02:00
|
|
|
#[repr(transparent)]
|
2022-08-07 22:40:29 +02:00
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
|
|
pub struct CodeIdx(pub usize);
|
|
|
|
|
2022-09-13 14:58:55 +02:00
|
|
|
impl AddAssign<usize> for CodeIdx {
|
|
|
|
fn add_assign(&mut self, rhs: usize) {
|
|
|
|
*self = CodeIdx(self.0 + rhs)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Sub<usize> for CodeIdx {
|
|
|
|
type Output = Self;
|
|
|
|
|
|
|
|
fn sub(self, rhs: usize) -> Self::Output {
|
|
|
|
CodeIdx(self.0 - rhs)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-14 06:07:40 +02:00
|
|
|
/// Index of a value in the runtime stack. This is an offset
|
|
|
|
/// *relative to* the VM value stack_base of the CallFrame
|
|
|
|
/// containing the opcode which contains this StackIdx.
|
2022-08-26 19:54:39 +02:00
|
|
|
#[repr(transparent)]
|
2022-09-03 02:06:20 +02:00
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd)]
|
2022-08-26 19:54:39 +02:00
|
|
|
pub struct StackIdx(pub usize);
|
|
|
|
|
2022-10-14 06:07:40 +02:00
|
|
|
/// Index of an upvalue within a closure's bound-variable upvalue
|
|
|
|
/// list. This is an absolute index into the Upvalues of the
|
|
|
|
/// CallFrame containing the opcode which contains this UpvalueIdx.
|
2022-08-26 20:48:51 +02:00
|
|
|
#[repr(transparent)]
|
2022-08-31 03:56:02 +02:00
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
2022-08-26 20:48:51 +02:00
|
|
|
pub struct UpvalueIdx(pub usize);
|
|
|
|
|
2022-08-26 19:48:39 +02:00
|
|
|
/// Offset by which an instruction pointer should change in a jump.
|
2022-08-26 19:46:43 +02:00
|
|
|
#[repr(transparent)]
|
2022-09-18 18:38:53 +02:00
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
2022-08-26 19:46:43 +02:00
|
|
|
pub struct JumpOffset(pub usize);
|
|
|
|
|
2022-08-26 19:58:18 +02:00
|
|
|
/// Provided count for an instruction (could represent e.g. a number
|
|
|
|
/// of elements).
|
|
|
|
#[repr(transparent)]
|
2022-09-18 18:38:53 +02:00
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
2022-08-26 19:58:18 +02:00
|
|
|
pub struct Count(pub usize);
|
|
|
|
|
2022-10-16 02:06:08 +02:00
|
|
|
/// All variants of this enum carry a bounded amount of data to
|
|
|
|
/// ensure that no heap allocations are needed for an Opcode.
|
2023-01-31 20:30:14 +01:00
|
|
|
///
|
|
|
|
/// In documentation comments, stack positions are referred to by
|
|
|
|
/// indices written in `{}` as such, where required:
|
|
|
|
///
|
|
|
|
/// ```notrust
|
|
|
|
/// --- top of the stack
|
|
|
|
/// /
|
|
|
|
/// v
|
|
|
|
/// [ ... | 3 | 2 | 1 | 0 ]
|
|
|
|
/// ^
|
|
|
|
/// /
|
|
|
|
/// 2 values deep ---
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// Unless otherwise specified, operations leave their result at the
|
|
|
|
/// top of the stack.
|
2022-08-12 16:19:14 +02:00
|
|
|
#[warn(variant_size_differences)]
|
2022-09-18 18:38:53 +02:00
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
2022-08-07 22:40:29 +02:00
|
|
|
pub enum OpCode {
|
2022-09-05 00:30:58 +02:00
|
|
|
/// Push a constant onto the stack.
|
2022-08-07 22:40:29 +02:00
|
|
|
OpConstant(ConstantIdx),
|
|
|
|
|
2023-01-31 20:30:14 +01:00
|
|
|
// Unary operators
|
2022-09-05 00:30:58 +02:00
|
|
|
/// Discard a value from the stack.
|
2022-08-11 12:12:07 +02:00
|
|
|
OpPop,
|
|
|
|
|
2023-01-31 20:30:14 +01:00
|
|
|
/// Invert the boolean at the top of the stack.
|
2022-08-08 01:51:28 +02:00
|
|
|
OpInvert,
|
2023-01-31 20:30:14 +01:00
|
|
|
|
|
|
|
// Binary operators
|
|
|
|
/// Invert the sign of the number at the top of the stack.
|
2022-08-08 01:51:28 +02:00
|
|
|
OpNegate,
|
|
|
|
|
2023-01-31 20:30:14 +01:00
|
|
|
/// Sum up the two numbers at the top of the stack.
|
2022-08-08 01:16:28 +02:00
|
|
|
OpAdd,
|
2023-01-31 20:30:14 +01:00
|
|
|
|
|
|
|
/// Subtract the number at {1} from the number at {2}.
|
2022-08-08 01:16:28 +02:00
|
|
|
OpSub,
|
2023-01-31 20:30:14 +01:00
|
|
|
|
|
|
|
/// Multiply the two numbers at the top of the stack.
|
2022-08-08 01:16:28 +02:00
|
|
|
OpMul,
|
2023-01-31 20:30:14 +01:00
|
|
|
|
|
|
|
/// Divide the two numbers at the top of the stack.
|
2022-08-08 01:16:28 +02:00
|
|
|
OpDiv,
|
2022-08-08 01:32:07 +02:00
|
|
|
|
2022-08-11 12:12:07 +02:00
|
|
|
// Comparison operators
|
2023-01-31 20:30:14 +01:00
|
|
|
/// Check the two values at the top of the stack for Nix-equality.
|
2022-08-08 01:51:28 +02:00
|
|
|
OpEqual,
|
2023-01-31 20:30:14 +01:00
|
|
|
|
|
|
|
/// Check whether the value at {2} is less than {1}.
|
2022-08-11 10:37:04 +02:00
|
|
|
OpLess,
|
2023-01-31 20:30:14 +01:00
|
|
|
|
|
|
|
/// Check whether the value at {2} is less than or equal to {1}.
|
2022-08-11 10:37:04 +02:00
|
|
|
OpLessOrEq,
|
2023-01-31 20:30:14 +01:00
|
|
|
|
|
|
|
/// Check whether the value at {2} is greater than {1}.
|
2022-08-11 10:37:04 +02:00
|
|
|
OpMore,
|
2023-01-31 20:30:14 +01:00
|
|
|
|
|
|
|
/// Check whether the value at {2} is greater than or equal to {1}.
|
2022-08-11 10:37:04 +02:00
|
|
|
OpMoreOrEq,
|
2022-08-09 15:53:09 +02:00
|
|
|
|
2022-08-11 12:12:07 +02:00
|
|
|
// Logical operators & generic jumps
|
2023-01-31 22:30:10 +01:00
|
|
|
/// Jump forward in the bytecode specified by the number of
|
|
|
|
/// instructions in its usize operand.
|
2022-08-26 19:46:43 +02:00
|
|
|
OpJump(JumpOffset),
|
2023-01-31 22:30:10 +01:00
|
|
|
|
|
|
|
/// Jump forward in the bytecode specified by the number of
|
|
|
|
/// instructions in its usize operand, *if* the value at the top
|
|
|
|
/// of the stack is `true`.
|
2022-08-26 19:46:43 +02:00
|
|
|
OpJumpIfTrue(JumpOffset),
|
2023-01-31 22:30:10 +01:00
|
|
|
|
|
|
|
/// Jump forward in the bytecode specified by the number of
|
|
|
|
/// instructions in its usize operand, *if* the value at the top
|
|
|
|
/// of the stack is `false`.
|
2022-08-26 19:46:43 +02:00
|
|
|
OpJumpIfFalse(JumpOffset),
|
2023-01-31 22:30:10 +01:00
|
|
|
|
2023-12-12 06:17:22 +01:00
|
|
|
/// Pop one stack item and jump forward in the bytecode
|
|
|
|
/// specified by the number of instructions in its usize
|
|
|
|
/// operand, *if* the value at the top of the stack is a
|
|
|
|
/// Value::Catchable.
|
|
|
|
OpJumpIfCatchable(JumpOffset),
|
|
|
|
|
2023-01-31 22:30:10 +01:00
|
|
|
/// Jump forward in the bytecode specified by the number of
|
|
|
|
/// instructions in its usize operand, *if* the value at the top
|
|
|
|
/// of the stack is the internal value representing a missing
|
|
|
|
/// attribute set key.
|
2022-08-26 19:46:43 +02:00
|
|
|
OpJumpIfNotFound(JumpOffset),
|
2023-01-31 22:30:10 +01:00
|
|
|
|
|
|
|
/// Jump forward in the bytecode specified by the number of
|
|
|
|
/// instructions in its usize operand, *if* the value at the top
|
|
|
|
/// of the stack is *not* the internal value requesting a
|
|
|
|
/// stack value finalisation.
|
fix(tvix/eval): only finalise formal arguments if defaulting
When dealing with a formal argument in a function argument pattern that
has a default expression, there are two different things that can happen
at runtime: Either we select its value from the passed attribute
successfully or we need to use the default expression. Both of these may
be thunks and both of these may need finalisers. However, in the former
case this is taken care of elsewhere, the value will always be finalised
already if necessary. In the latter case we may need to finalise the
thunk resulting from the default expression. However, the thunk
corresponding to the expression may never end up in the local's stack
slot. Since finalisation goes by stack slot (and not constants), we need
to prevent a case where we don't fall back to the default expression,
but finalise anyways.
Previously, we worked around this by making `OpFinalise` ignore
non-thunks. Since finalisation of already evaluated thunks still
crashed, the faulty compilation of function pattern arguments could
still cause a crash.
As a new approach, we reinstate the old behavior of `OpFinalise` to
crash whenever encountering something that is either not a thunk or
doesn't need finalisation. This can also help catching (similar)
miscompilations in the future. To then prevent the crash, we need to
track whether we have fallen back or not at runtime. This is done using
an additional phantom on the stack that holds a new `FinaliseRequest`
value. When it comes to finalisation we check this value and
conditionally execute `OpFinalise` based on its value.
Resolves b/261 and b/265 (partially).
Change-Id: Ic04fb80ec671a2ba11fa645090769c335fb7f58b
Reviewed-on: https://cl.tvl.fyi/c/depot/+/8705
Reviewed-by: tazjin <tazjin@tvl.su>
Tested-by: BuildkiteCI
Autosubmit: sterni <sternenseemann@systemli.org>
2023-06-03 02:10:31 +02:00
|
|
|
OpJumpIfNoFinaliseRequest(JumpOffset),
|
2022-08-11 12:12:07 +02:00
|
|
|
|
2022-08-09 15:53:09 +02:00
|
|
|
// Attribute sets
|
2022-09-18 22:07:43 +02:00
|
|
|
/// Construct an attribute set from the given number of key-value pairs on the top of the stack
|
|
|
|
///
|
|
|
|
/// Note that this takes the count of *pairs*, not the number of *stack values* - the actual
|
|
|
|
/// number of values popped off the stack will be twice the argument to this op
|
2022-08-26 19:58:18 +02:00
|
|
|
OpAttrs(Count),
|
2023-01-31 22:24:34 +01:00
|
|
|
/// Merge the attribute set at {2} into the attribute set at {1},
|
|
|
|
/// and leave the new set at the top of the stack.
|
2022-08-10 20:01:15 +02:00
|
|
|
OpAttrsUpdate,
|
2023-01-31 22:24:34 +01:00
|
|
|
|
|
|
|
/// Select the attribute with the name at {1} from the set at {2}.
|
2022-08-11 14:29:11 +02:00
|
|
|
OpAttrsSelect,
|
2023-01-31 22:24:34 +01:00
|
|
|
|
|
|
|
/// Select the attribute with the name at {1} from the set at {2}, but leave
|
|
|
|
/// a `Value::AttrNotFound` in the stack instead of failing if it is
|
|
|
|
/// missing.
|
2022-08-26 17:40:55 +02:00
|
|
|
OpAttrsTrySelect,
|
2023-01-31 22:24:34 +01:00
|
|
|
|
|
|
|
/// Check for the presence of the attribute with the name at {1} in the set
|
|
|
|
/// at {2}.
|
2022-09-17 18:10:40 +02:00
|
|
|
OpHasAttr,
|
2022-08-09 16:11:02 +02:00
|
|
|
|
2022-10-13 05:53:03 +02:00
|
|
|
/// Throw an error if the attribute set at the top of the stack has any attributes
|
|
|
|
/// other than those listed in the formals of the current lambda
|
|
|
|
///
|
|
|
|
/// Panics if the current frame is not a lambda with formals
|
|
|
|
OpValidateClosedFormals,
|
|
|
|
|
2022-08-14 23:13:57 +02:00
|
|
|
// `with`-handling
|
2023-01-31 22:30:10 +01:00
|
|
|
/// Push a value onto the runtime `with`-stack to enable dynamic identifier
|
|
|
|
/// resolution. The absolute stack index of the value is supplied as a usize
|
|
|
|
/// operand.
|
2022-08-26 19:54:39 +02:00
|
|
|
OpPushWith(StackIdx),
|
2023-01-31 22:30:10 +01:00
|
|
|
|
|
|
|
/// Pop the last runtime `with`-stack element.
|
2022-08-14 23:38:30 +02:00
|
|
|
OpPopWith,
|
2023-01-31 22:30:10 +01:00
|
|
|
|
|
|
|
/// Dynamically resolve an identifier with the name at {1} from the runtime
|
|
|
|
/// `with`-stack.
|
2022-08-15 00:13:17 +02:00
|
|
|
OpResolveWith,
|
2022-08-14 23:13:57 +02:00
|
|
|
|
2022-08-09 16:11:02 +02:00
|
|
|
// Lists
|
2023-01-31 22:30:10 +01:00
|
|
|
/// Construct a list from the given number of values at the top of the
|
|
|
|
/// stack.
|
2022-08-26 19:58:18 +02:00
|
|
|
OpList(Count),
|
2023-01-31 22:30:10 +01:00
|
|
|
|
|
|
|
/// Concatenate the lists at {2} and {1}.
|
2022-08-11 10:50:38 +02:00
|
|
|
OpConcat,
|
2022-08-09 16:44:34 +02:00
|
|
|
|
|
|
|
// Strings
|
2023-01-31 22:30:10 +01:00
|
|
|
/// Interpolate the given number of string fragments into a single string.
|
2022-08-26 19:58:18 +02:00
|
|
|
OpInterpolate(Count),
|
2023-01-31 22:30:10 +01:00
|
|
|
|
2023-12-13 05:47:31 +01:00
|
|
|
/// Force the Value on the stack and coerce it to a string
|
|
|
|
OpCoerceToString(crate::CoercionKind),
|
2022-08-11 13:56:27 +02:00
|
|
|
|
2022-10-10 05:46:51 +02:00
|
|
|
// Paths
|
2022-10-10 20:43:51 +02:00
|
|
|
/// Attempt to resolve the Value on the stack using the configured [`NixSearchPath`][]
|
2022-10-10 05:46:51 +02:00
|
|
|
///
|
2022-10-10 20:43:51 +02:00
|
|
|
/// [`NixSearchPath`]: crate::nix_search_path::NixSearchPath
|
2022-10-10 05:46:51 +02:00
|
|
|
OpFindFile,
|
|
|
|
|
2022-10-15 16:42:27 +02:00
|
|
|
/// Attempt to resolve a path literal relative to the home dir
|
|
|
|
OpResolveHomePath,
|
|
|
|
|
2022-08-11 13:56:27 +02:00
|
|
|
// Type assertion operators
|
2023-01-31 22:30:10 +01:00
|
|
|
/// Assert that the value at {1} is a boolean, and fail with a runtime error
|
|
|
|
/// otherwise.
|
2022-08-11 13:56:27 +02:00
|
|
|
OpAssertBool,
|
2023-06-02 22:38:00 +02:00
|
|
|
OpAssertAttrs,
|
2022-08-13 16:34:20 +02:00
|
|
|
|
2022-09-05 00:30:58 +02:00
|
|
|
/// Access local identifiers with statically known positions.
|
2022-08-26 19:54:39 +02:00
|
|
|
OpGetLocal(StackIdx),
|
2022-08-13 19:17:25 +02:00
|
|
|
|
2022-09-05 00:30:58 +02:00
|
|
|
/// Close scopes while leaving their expression value around.
|
2022-08-26 19:58:18 +02:00
|
|
|
OpCloseScope(Count), // number of locals to pop
|
2022-08-16 14:53:35 +02:00
|
|
|
|
2022-10-10 18:56:11 +02:00
|
|
|
/// Return an error indicating that an `assert` failed
|
|
|
|
OpAssertFail,
|
2022-08-24 01:26:58 +02:00
|
|
|
|
2022-08-26 23:21:08 +02:00
|
|
|
// Lambdas & closures
|
2023-01-31 22:30:10 +01:00
|
|
|
/// Call the value at {1} in a new VM callframe
|
2022-08-24 01:26:58 +02:00
|
|
|
OpCall,
|
2023-01-31 22:30:10 +01:00
|
|
|
|
|
|
|
/// Retrieve the upvalue at the given index from the closure or thunk
|
|
|
|
/// currently under evaluation.
|
2022-08-26 20:48:51 +02:00
|
|
|
OpGetUpvalue(UpvalueIdx),
|
2023-01-31 22:30:10 +01:00
|
|
|
|
|
|
|
/// Construct a closure which has upvalues but no self-references
|
2022-08-26 23:21:08 +02:00
|
|
|
OpClosure(ConstantIdx),
|
2023-01-31 22:30:10 +01:00
|
|
|
|
|
|
|
/// Construct a closure which has self-references (direct or via upvalues)
|
2022-10-16 01:10:10 +02:00
|
|
|
OpThunkClosure(ConstantIdx),
|
2023-01-31 22:30:10 +01:00
|
|
|
|
|
|
|
/// Construct a suspended thunk, used to delay a computation for laziness.
|
2022-10-16 01:10:10 +02:00
|
|
|
OpThunkSuspended(ConstantIdx),
|
2023-01-31 22:30:10 +01:00
|
|
|
|
|
|
|
/// Force the value at {1} until it is a `Thunk::Evaluated`.
|
2022-08-29 17:33:02 +02:00
|
|
|
OpForce,
|
2022-08-28 22:53:20 +02:00
|
|
|
|
2022-10-25 11:14:39 +02:00
|
|
|
/// Finalise initialisation of the upvalues of the value in the given stack
|
|
|
|
/// index (which must be a Value::Thunk) after the scope is fully bound.
|
2022-08-28 16:28:20 +02:00
|
|
|
OpFinalise(StackIdx),
|
|
|
|
|
refactor(tvix/eval): flatten call stack of VM using generators
Warning: This is probably the biggest refactor in tvix-eval history,
so far.
This replaces all instances of trampolines and recursion during
evaluation of the VM loop with generators. A generator is an
asynchronous function that can be suspended to yield a message (in our
case, vm::generators::GeneratorRequest) and receive a
response (vm::generators::GeneratorResponsee).
The `genawaiter` crate provides an interpreter for generators that can
drive their execution and lets us move control flow between the VM and
suspended generators.
To do this, massive changes have occured basically everywhere in the
code. On a high-level:
1. The VM is now organised around a frame stack. A frame is either a
call frame (execution of Tvix bytecode) or a generator frame (a
running or suspended generator).
The VM has an outer loop that pops a frame off the frame stack, and
then enters an inner loop either driving the execution of the
bytecode or the execution of a generator.
Both types of frames have several branches that can result in the
frame re-enqueuing itself, and enqueuing some other work (in the
form of a different frame) on top of itself. The VM will eventually
resume the frame when everything "above" it has been suspended.
In this way, the VM's new frame stack takes over much of the work
that was previously achieved by recursion.
2. All methods previously taking a VM have been refactored into async
functions that instead emit/receive generator messages for
communication with the VM.
Notably, this includes *all* builtins.
This has had some other effects:
- Some test have been removed or commented out, either because they
tested code that was mostly already dead (nix_eq) or because they
now require generator scaffolding which we do not have in place for
tests (yet).
- Because generator functions are technically async (though no async
IO is involved), we lose the ability to use much of the Rust
standard library e.g. in builtins. This has led to many algorithms
being unrolled into iterative versions instead of iterator
combinations, and things like sorting had to be implemented from scratch.
- Many call sites that previously saw a `Result<..., ErrorKind>`
bubble up now only see the result value, as the error handling is
encapsulated within the generator loop.
This reduces number of places inside of builtin implementations
where error context can be attached to calls that can fail.
Currently what we gain in this tradeoff is significantly more
detailed span information (which we still need to bubble up, this
commit does not change the error display).
We'll need to do some analysis later of how useful the errors turn
out to be and potentially introduce some methods for attaching
context to a generator frame again.
This change is very difficult to do in stages, as it is very much an
"all or nothing" change that affects huge parts of the codebase. I've
tried to isolate changes that can be isolated into the parent CLs of
this one, but this change is still quite difficult to wrap one's mind
and I'm available to discuss it and explain things to any reviewer.
Fixes: b/238, b/237, b/251 and potentially others.
Change-Id: I39244163ff5bbecd169fe7b274df19262b515699
Reviewed-on: https://cl.tvl.fyi/c/depot/+/8104
Reviewed-by: raitobezarius <tvl@lahfa.xyz>
Reviewed-by: Adam Joseph <adam@westernsemico.com>
Tested-by: BuildkiteCI
2023-02-14 13:02:39 +01:00
|
|
|
/// Final instruction emitted in a chunk. Does not have an
|
|
|
|
/// inherent effect, but can simplify VM logic as a marker in some
|
|
|
|
/// cases.
|
|
|
|
///
|
|
|
|
/// Can be thought of as "returning" the value to the parent
|
|
|
|
/// frame, hence the name.
|
|
|
|
OpReturn,
|
|
|
|
|
2022-10-16 01:10:10 +02:00
|
|
|
// [`OpClosure`], [`OpThunkSuspended`], and [`OpThunkClosure`] have a
|
|
|
|
// variable number of arguments to the instruction, which is
|
|
|
|
// represented here by making their data part of the opcodes.
|
|
|
|
// Each of these two opcodes has a `ConstantIdx`, which must
|
|
|
|
// reference a `Value::Blueprint(Lambda)`. The `upvalue_count`
|
|
|
|
// field in that `Lambda` indicates the number of arguments it
|
|
|
|
// takes, and the opcode must be followed by exactly this number
|
|
|
|
// of `Data*` opcodes. The VM skips over these by advancing the
|
|
|
|
// instruction pointer.
|
2022-08-26 23:21:08 +02:00
|
|
|
//
|
2022-10-16 02:06:08 +02:00
|
|
|
// It is illegal for a `Data*` opcode to appear anywhere else.
|
2022-10-25 11:14:39 +02:00
|
|
|
/// Populate a static upvalue by copying from the stack immediately.
|
2022-10-25 11:16:59 +02:00
|
|
|
DataStackIdx(StackIdx),
|
2022-10-25 11:14:39 +02:00
|
|
|
/// Populate a static upvalue of a thunk by copying it the stack, but do
|
|
|
|
/// when the thunk is finalised (by OpFinalise) rather than immediately.
|
2022-08-28 15:50:46 +02:00
|
|
|
DataDeferredLocal(StackIdx),
|
2022-10-25 11:14:39 +02:00
|
|
|
/// Populate a static upvalue by copying it from the upvalues of an
|
|
|
|
/// enclosing scope.
|
2022-08-26 23:21:08 +02:00
|
|
|
DataUpvalueIdx(UpvalueIdx),
|
2022-10-25 11:14:39 +02:00
|
|
|
/// Populate dynamic upvalues by saving a copy of the with-stack.
|
2022-09-06 22:13:48 +02:00
|
|
|
DataCaptureWith,
|
2022-08-07 22:40:29 +02:00
|
|
|
}
|