refactor(tvix/eval): reorder bytecode operations match by frequency
This reorders the operations in the VM's main `match` statement while evaluating bytecode according to the frequency with which these operations appear in some nixpkgs evaluations. I used raw data that looks like this: https://gist.github.com/tazjin/63d0788a78eb8575b04defaad4ef610d This has a small but noticeable impact on evaluation performance. No operations have changed in any way, this is purely moving code around. Change-Id: Iaa4ef4f0577e98144e8905fec88149c41e8c315c Reviewed-on: https://cl.tvl.fyi/c/depot/+/8262 Reviewed-by: raitobezarius <tvl@lahfa.xyz> Reviewed-by: flokli <flokli@flokli.de> Tested-by: BuildkiteCI
This commit is contained in:
parent
c37e41d583
commit
94513525b9
1 changed files with 287 additions and 288 deletions
|
@ -382,8 +382,54 @@ impl<'o> VM<'o> {
|
||||||
let op = frame.inc_ip();
|
let op = frame.inc_ip();
|
||||||
self.observer.observe_execute_op(frame.ip, &op, &self.stack);
|
self.observer.observe_execute_op(frame.ip, &op, &self.stack);
|
||||||
|
|
||||||
// TODO: might be useful to reorder ops with most frequent ones first
|
|
||||||
match op {
|
match op {
|
||||||
|
OpCode::OpThunkSuspended(idx) | OpCode::OpThunkClosure(idx) => {
|
||||||
|
let blueprint = match &frame.chunk()[idx] {
|
||||||
|
Value::Blueprint(lambda) => lambda.clone(),
|
||||||
|
_ => panic!("compiler bug: non-blueprint in blueprint slot"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let upvalue_count = blueprint.upvalue_count;
|
||||||
|
let thunk = if matches!(op, OpCode::OpThunkClosure(_)) {
|
||||||
|
debug_assert!(
|
||||||
|
upvalue_count > 0,
|
||||||
|
"OpThunkClosure should not be called for plain lambdas"
|
||||||
|
);
|
||||||
|
Thunk::new_closure(blueprint)
|
||||||
|
} else {
|
||||||
|
Thunk::new_suspended(blueprint, frame.current_light_span())
|
||||||
|
};
|
||||||
|
let upvalues = thunk.upvalues_mut();
|
||||||
|
self.stack.push(Value::Thunk(thunk.clone()));
|
||||||
|
|
||||||
|
// From this point on we internally mutate the
|
||||||
|
// upvalues. The closure (if `is_closure`) is
|
||||||
|
// already in its stack slot, which means that it
|
||||||
|
// can capture itself as an upvalue for
|
||||||
|
// self-recursion.
|
||||||
|
self.populate_upvalues(&mut frame, upvalue_count, upvalues)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::OpForce => {
|
||||||
|
if let Some(Value::Thunk(_)) = self.stack.last() {
|
||||||
|
let thunk = match self.stack_pop() {
|
||||||
|
Value::Thunk(t) => t,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let gen_span = frame.current_light_span();
|
||||||
|
|
||||||
|
self.push_call_frame(span, frame);
|
||||||
|
self.enqueue_generator("force", gen_span, |co| thunk.force(co));
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::OpGetUpvalue(upv_idx) => {
|
||||||
|
let value = frame.upvalue(upv_idx).clone();
|
||||||
|
self.stack.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
// Discard the current frame.
|
// Discard the current frame.
|
||||||
OpCode::OpReturn => {
|
OpCode::OpReturn => {
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
|
@ -394,10 +440,250 @@ impl<'o> VM<'o> {
|
||||||
self.stack.push(c);
|
self.stack.push(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OpCode::OpCall => {
|
||||||
|
let callable = self.stack_pop();
|
||||||
|
self.call_value(frame.current_light_span(), Some(frame), callable)?;
|
||||||
|
|
||||||
|
// exit this loop and let the outer loop enter the new call
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the given number of elements from the stack,
|
||||||
|
// but retain the top value.
|
||||||
|
OpCode::OpCloseScope(Count(count)) => {
|
||||||
|
// Immediately move the top value into the right
|
||||||
|
// position.
|
||||||
|
let target_idx = self.stack.len() - 1 - count;
|
||||||
|
self.stack[target_idx] = self.stack_pop();
|
||||||
|
|
||||||
|
// Then drop the remaining values.
|
||||||
|
for _ in 0..(count - 1) {
|
||||||
|
self.stack.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::OpClosure(idx) => {
|
||||||
|
let blueprint = match &frame.chunk()[idx] {
|
||||||
|
Value::Blueprint(lambda) => lambda.clone(),
|
||||||
|
_ => panic!("compiler bug: non-blueprint in blueprint slot"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let upvalue_count = blueprint.upvalue_count;
|
||||||
|
debug_assert!(
|
||||||
|
upvalue_count > 0,
|
||||||
|
"OpClosure should not be called for plain lambdas"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut upvalues = Upvalues::with_capacity(blueprint.upvalue_count);
|
||||||
|
self.populate_upvalues(&mut frame, upvalue_count, &mut upvalues)?;
|
||||||
|
self.stack
|
||||||
|
.push(Value::Closure(Rc::new(Closure::new_with_upvalues(
|
||||||
|
Rc::new(upvalues),
|
||||||
|
blueprint,
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::OpAttrsSelect => {
|
||||||
|
let key = self.stack_pop().to_str().with_span(&frame)?;
|
||||||
|
let attrs = self.stack_pop().to_attrs().with_span(&frame)?;
|
||||||
|
|
||||||
|
match attrs.select(key.as_str()) {
|
||||||
|
Some(value) => self.stack.push(value.clone()),
|
||||||
|
|
||||||
|
None => {
|
||||||
|
return Err(frame.error(ErrorKind::AttributeNotFound {
|
||||||
|
name: key.as_str().to_string(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::OpJumpIfFalse(JumpOffset(offset)) => {
|
||||||
|
debug_assert!(offset != 0);
|
||||||
|
if !self.stack_peek(0).as_bool().with_span(&frame)? {
|
||||||
|
frame.ip += offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
OpCode::OpPop => {
|
OpCode::OpPop => {
|
||||||
self.stack.pop();
|
self.stack.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OpCode::OpAttrsTrySelect => {
|
||||||
|
let key = self.stack_pop().to_str().with_span(&frame)?;
|
||||||
|
let value = match self.stack_pop() {
|
||||||
|
Value::Attrs(attrs) => match attrs.select(key.as_str()) {
|
||||||
|
Some(value) => value.clone(),
|
||||||
|
None => Value::AttrNotFound,
|
||||||
|
},
|
||||||
|
|
||||||
|
_ => Value::AttrNotFound,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.stack.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::OpGetLocal(StackIdx(local_idx)) => {
|
||||||
|
let idx = frame.stack_offset + local_idx;
|
||||||
|
self.stack.push(self.stack[idx].clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::OpJumpIfNotFound(JumpOffset(offset)) => {
|
||||||
|
debug_assert!(offset != 0);
|
||||||
|
if matches!(self.stack_peek(0), Value::AttrNotFound) {
|
||||||
|
self.stack_pop();
|
||||||
|
frame.ip += offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::OpJump(JumpOffset(offset)) => {
|
||||||
|
debug_assert!(offset != 0);
|
||||||
|
frame.ip += offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::OpEqual => {
|
||||||
|
let b = self.stack_pop();
|
||||||
|
let a = self.stack_pop();
|
||||||
|
let gen_span = frame.current_light_span();
|
||||||
|
self.push_call_frame(span, frame);
|
||||||
|
self.enqueue_generator("nix_eq", gen_span, |co| {
|
||||||
|
a.nix_eq(b, co, PointerEquality::ForbidAll)
|
||||||
|
});
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// These assertion operations error out if the stack
|
||||||
|
// top is not of the expected type. This is necessary
|
||||||
|
// to implement some specific behaviours of Nix
|
||||||
|
// exactly.
|
||||||
|
OpCode::OpAssertBool => {
|
||||||
|
let val = self.stack_peek(0);
|
||||||
|
if !val.is_bool() {
|
||||||
|
return Err(frame.error(ErrorKind::TypeError {
|
||||||
|
expected: "bool",
|
||||||
|
actual: val.type_of(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::OpAttrs(Count(count)) => self.run_attrset(&frame, count)?,
|
||||||
|
|
||||||
|
OpCode::OpAttrsUpdate => {
|
||||||
|
let rhs = self.stack_pop().to_attrs().with_span(&frame)?;
|
||||||
|
let lhs = self.stack_pop().to_attrs().with_span(&frame)?;
|
||||||
|
|
||||||
|
self.stack.push(Value::attrs(lhs.update(*rhs)))
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::OpInvert => {
|
||||||
|
let v = self.stack_pop().as_bool().with_span(&frame)?;
|
||||||
|
self.stack.push(Value::Bool(!v));
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::OpList(Count(count)) => {
|
||||||
|
let list =
|
||||||
|
NixList::construct(count, self.stack.split_off(self.stack.len() - count));
|
||||||
|
|
||||||
|
self.stack.push(Value::List(list));
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::OpJumpIfTrue(JumpOffset(offset)) => {
|
||||||
|
debug_assert!(offset != 0);
|
||||||
|
if self.stack_peek(0).as_bool().with_span(&frame)? {
|
||||||
|
frame.ip += offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::OpHasAttr => {
|
||||||
|
let key = self.stack_pop().to_str().with_span(&frame)?;
|
||||||
|
let result = match self.stack_pop() {
|
||||||
|
Value::Attrs(attrs) => attrs.contains(key.as_str()),
|
||||||
|
|
||||||
|
// Nix allows use of `?` on non-set types, but
|
||||||
|
// always returns false in those cases.
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.stack.push(Value::Bool(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::OpConcat => {
|
||||||
|
let rhs = self.stack_pop().to_list().with_span(&frame)?.into_inner();
|
||||||
|
let lhs = self.stack_pop().to_list().with_span(&frame)?.into_inner();
|
||||||
|
self.stack.push(Value::List(NixList::from(lhs + rhs)))
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::OpResolveWith => {
|
||||||
|
let ident = self.stack_pop().to_str().with_span(&frame)?;
|
||||||
|
|
||||||
|
// Re-enqueue this frame.
|
||||||
|
let op_span = frame.current_light_span();
|
||||||
|
self.push_call_frame(span, frame);
|
||||||
|
|
||||||
|
// Construct a generator frame doing the lookup in constant
|
||||||
|
// stack space.
|
||||||
|
let with_stack_len = self.with_stack.len();
|
||||||
|
let closed_with_stack_len = self
|
||||||
|
.last_call_frame()
|
||||||
|
.map(|frame| frame.upvalues.with_stack_len())
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
|
self.enqueue_generator("resolve_with", op_span, |co| {
|
||||||
|
resolve_with(
|
||||||
|
co,
|
||||||
|
ident.as_str().to_owned(),
|
||||||
|
with_stack_len,
|
||||||
|
closed_with_stack_len,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::OpFinalise(StackIdx(idx)) => {
|
||||||
|
match &self.stack[frame.stack_offset + idx] {
|
||||||
|
Value::Closure(_) => panic!("attempted to finalise a closure"),
|
||||||
|
Value::Thunk(thunk) => thunk.finalise(&self.stack[frame.stack_offset..]),
|
||||||
|
|
||||||
|
// In functions with "formals" attributes, it is
|
||||||
|
// possible for `OpFinalise` to be called on a
|
||||||
|
// non-capturing value, in which case it is a no-op.
|
||||||
|
//
|
||||||
|
// TODO: detect this in some phase and skip the finalise; fail here
|
||||||
|
_ => { /* TODO: panic here again to catch bugs */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::OpCoerceToString => {
|
||||||
|
let value = self.stack_pop();
|
||||||
|
let gen_span = frame.current_light_span();
|
||||||
|
self.push_call_frame(span, frame);
|
||||||
|
|
||||||
|
self.enqueue_generator("coerce_to_string", gen_span, |co| {
|
||||||
|
value.coerce_to_string(co, CoercionKind::Weak)
|
||||||
|
});
|
||||||
|
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::OpInterpolate(Count(count)) => self.run_interpolate(&frame, count)?,
|
||||||
|
|
||||||
|
OpCode::OpValidateClosedFormals => {
|
||||||
|
let formals = frame.lambda.formals.as_ref().expect(
|
||||||
|
"OpValidateClosedFormals called within the frame of a lambda without formals",
|
||||||
|
);
|
||||||
|
|
||||||
|
let args = self.stack_peek(0).to_attrs().with_span(&frame)?;
|
||||||
|
for arg in args.keys() {
|
||||||
|
if !formals.contains(arg) {
|
||||||
|
return Err(frame.error(ErrorKind::UnexpectedArgument {
|
||||||
|
arg: arg.clone(),
|
||||||
|
formals_span: formals.span,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
OpCode::OpAdd => {
|
OpCode::OpAdd => {
|
||||||
let b = self.stack_pop();
|
let b = self.stack_pop();
|
||||||
let a = self.stack_pop();
|
let a = self.stack_pop();
|
||||||
|
@ -442,11 +728,6 @@ impl<'o> VM<'o> {
|
||||||
self.stack.push(result);
|
self.stack.push(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
OpCode::OpInvert => {
|
|
||||||
let v = self.stack_pop().as_bool().with_span(&frame)?;
|
|
||||||
self.stack.push(Value::Bool(!v));
|
|
||||||
}
|
|
||||||
|
|
||||||
OpCode::OpNegate => match self.stack_pop() {
|
OpCode::OpNegate => match self.stack_pop() {
|
||||||
Value::Integer(i) => self.stack.push(Value::Integer(-i)),
|
Value::Integer(i) => self.stack.push(Value::Integer(-i)),
|
||||||
Value::Float(f) => self.stack.push(Value::Float(-f)),
|
Value::Float(f) => self.stack.push(Value::Float(-f)),
|
||||||
|
@ -458,116 +739,11 @@ impl<'o> VM<'o> {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
OpCode::OpEqual => {
|
|
||||||
let b = self.stack_pop();
|
|
||||||
let a = self.stack_pop();
|
|
||||||
let gen_span = frame.current_light_span();
|
|
||||||
self.push_call_frame(span, frame);
|
|
||||||
self.enqueue_generator("nix_eq", gen_span, |co| {
|
|
||||||
a.nix_eq(b, co, PointerEquality::ForbidAll)
|
|
||||||
});
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
OpCode::OpLess => cmp_op!(self, frame, span, <),
|
OpCode::OpLess => cmp_op!(self, frame, span, <),
|
||||||
OpCode::OpLessOrEq => cmp_op!(self, frame, span, <=),
|
OpCode::OpLessOrEq => cmp_op!(self, frame, span, <=),
|
||||||
OpCode::OpMore => cmp_op!(self, frame, span, >),
|
OpCode::OpMore => cmp_op!(self, frame, span, >),
|
||||||
OpCode::OpMoreOrEq => cmp_op!(self, frame, span, >=),
|
OpCode::OpMoreOrEq => cmp_op!(self, frame, span, >=),
|
||||||
|
|
||||||
OpCode::OpAttrs(Count(count)) => self.run_attrset(&frame, count)?,
|
|
||||||
|
|
||||||
OpCode::OpAttrsUpdate => {
|
|
||||||
let rhs = self.stack_pop().to_attrs().with_span(&frame)?;
|
|
||||||
let lhs = self.stack_pop().to_attrs().with_span(&frame)?;
|
|
||||||
|
|
||||||
self.stack.push(Value::attrs(lhs.update(*rhs)))
|
|
||||||
}
|
|
||||||
|
|
||||||
OpCode::OpAttrsSelect => {
|
|
||||||
let key = self.stack_pop().to_str().with_span(&frame)?;
|
|
||||||
let attrs = self.stack_pop().to_attrs().with_span(&frame)?;
|
|
||||||
|
|
||||||
match attrs.select(key.as_str()) {
|
|
||||||
Some(value) => self.stack.push(value.clone()),
|
|
||||||
|
|
||||||
None => {
|
|
||||||
return Err(frame.error(ErrorKind::AttributeNotFound {
|
|
||||||
name: key.as_str().to_string(),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OpCode::OpAttrsTrySelect => {
|
|
||||||
let key = self.stack_pop().to_str().with_span(&frame)?;
|
|
||||||
let value = match self.stack_pop() {
|
|
||||||
Value::Attrs(attrs) => match attrs.select(key.as_str()) {
|
|
||||||
Some(value) => value.clone(),
|
|
||||||
None => Value::AttrNotFound,
|
|
||||||
},
|
|
||||||
|
|
||||||
_ => Value::AttrNotFound,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.stack.push(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
OpCode::OpHasAttr => {
|
|
||||||
let key = self.stack_pop().to_str().with_span(&frame)?;
|
|
||||||
let result = match self.stack_pop() {
|
|
||||||
Value::Attrs(attrs) => attrs.contains(key.as_str()),
|
|
||||||
|
|
||||||
// Nix allows use of `?` on non-set types, but
|
|
||||||
// always returns false in those cases.
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.stack.push(Value::Bool(result));
|
|
||||||
}
|
|
||||||
|
|
||||||
OpCode::OpValidateClosedFormals => {
|
|
||||||
let formals = frame.lambda.formals.as_ref().expect(
|
|
||||||
"OpValidateClosedFormals called within the frame of a lambda without formals",
|
|
||||||
);
|
|
||||||
|
|
||||||
let args = self.stack_peek(0).to_attrs().with_span(&frame)?;
|
|
||||||
for arg in args.keys() {
|
|
||||||
if !formals.contains(arg) {
|
|
||||||
return Err(frame.error(ErrorKind::UnexpectedArgument {
|
|
||||||
arg: arg.clone(),
|
|
||||||
formals_span: formals.span,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OpCode::OpList(Count(count)) => {
|
|
||||||
let list =
|
|
||||||
NixList::construct(count, self.stack.split_off(self.stack.len() - count));
|
|
||||||
|
|
||||||
self.stack.push(Value::List(list));
|
|
||||||
}
|
|
||||||
|
|
||||||
OpCode::OpConcat => {
|
|
||||||
let rhs = self.stack_pop().to_list().with_span(&frame)?.into_inner();
|
|
||||||
let lhs = self.stack_pop().to_list().with_span(&frame)?.into_inner();
|
|
||||||
self.stack.push(Value::List(NixList::from(lhs + rhs)))
|
|
||||||
}
|
|
||||||
|
|
||||||
OpCode::OpInterpolate(Count(count)) => self.run_interpolate(&frame, count)?,
|
|
||||||
|
|
||||||
OpCode::OpCoerceToString => {
|
|
||||||
let value = self.stack_pop();
|
|
||||||
let gen_span = frame.current_light_span();
|
|
||||||
self.push_call_frame(span, frame);
|
|
||||||
|
|
||||||
self.enqueue_generator("coerce_to_string", gen_span, |co| {
|
|
||||||
value.coerce_to_string(co, CoercionKind::Weak)
|
|
||||||
});
|
|
||||||
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
OpCode::OpFindFile => match self.stack_pop() {
|
OpCode::OpFindFile => match self.stack_pop() {
|
||||||
Value::UnresolvedPath(path) => {
|
Value::UnresolvedPath(path) => {
|
||||||
let resolved = self
|
let resolved = self
|
||||||
|
@ -600,193 +776,16 @@ impl<'o> VM<'o> {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
OpCode::OpJump(JumpOffset(offset)) => {
|
|
||||||
debug_assert!(offset != 0);
|
|
||||||
frame.ip += offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
OpCode::OpJumpIfTrue(JumpOffset(offset)) => {
|
|
||||||
debug_assert!(offset != 0);
|
|
||||||
if self.stack_peek(0).as_bool().with_span(&frame)? {
|
|
||||||
frame.ip += offset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OpCode::OpJumpIfFalse(JumpOffset(offset)) => {
|
|
||||||
debug_assert!(offset != 0);
|
|
||||||
if !self.stack_peek(0).as_bool().with_span(&frame)? {
|
|
||||||
frame.ip += offset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OpCode::OpJumpIfNotFound(JumpOffset(offset)) => {
|
|
||||||
debug_assert!(offset != 0);
|
|
||||||
if matches!(self.stack_peek(0), Value::AttrNotFound) {
|
|
||||||
self.stack_pop();
|
|
||||||
frame.ip += offset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// These assertion operations error out if the stack
|
|
||||||
// top is not of the expected type. This is necessary
|
|
||||||
// to implement some specific behaviours of Nix
|
|
||||||
// exactly.
|
|
||||||
OpCode::OpAssertBool => {
|
|
||||||
let val = self.stack_peek(0);
|
|
||||||
if !val.is_bool() {
|
|
||||||
return Err(frame.error(ErrorKind::TypeError {
|
|
||||||
expected: "bool",
|
|
||||||
actual: val.type_of(),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the given number of elements from the stack,
|
|
||||||
// but retain the top value.
|
|
||||||
OpCode::OpCloseScope(Count(count)) => {
|
|
||||||
// Immediately move the top value into the right
|
|
||||||
// position.
|
|
||||||
let target_idx = self.stack.len() - 1 - count;
|
|
||||||
self.stack[target_idx] = self.stack_pop();
|
|
||||||
|
|
||||||
// Then drop the remaining values.
|
|
||||||
for _ in 0..(count - 1) {
|
|
||||||
self.stack.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OpCode::OpGetLocal(StackIdx(local_idx)) => {
|
|
||||||
let idx = frame.stack_offset + local_idx;
|
|
||||||
self.stack.push(self.stack[idx].clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
OpCode::OpPushWith(StackIdx(idx)) => self.with_stack.push(frame.stack_offset + idx),
|
OpCode::OpPushWith(StackIdx(idx)) => self.with_stack.push(frame.stack_offset + idx),
|
||||||
|
|
||||||
OpCode::OpPopWith => {
|
OpCode::OpPopWith => {
|
||||||
self.with_stack.pop();
|
self.with_stack.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
OpCode::OpResolveWith => {
|
|
||||||
let ident = self.stack_pop().to_str().with_span(&frame)?;
|
|
||||||
|
|
||||||
// Re-enqueue this frame.
|
|
||||||
let op_span = frame.current_light_span();
|
|
||||||
self.push_call_frame(span, frame);
|
|
||||||
|
|
||||||
// Construct a generator frame doing the lookup in constant
|
|
||||||
// stack space.
|
|
||||||
let with_stack_len = self.with_stack.len();
|
|
||||||
let closed_with_stack_len = self
|
|
||||||
.last_call_frame()
|
|
||||||
.map(|frame| frame.upvalues.with_stack_len())
|
|
||||||
.unwrap_or(0);
|
|
||||||
|
|
||||||
self.enqueue_generator("resolve_with", op_span, |co| {
|
|
||||||
resolve_with(
|
|
||||||
co,
|
|
||||||
ident.as_str().to_owned(),
|
|
||||||
with_stack_len,
|
|
||||||
closed_with_stack_len,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
OpCode::OpAssertFail => {
|
OpCode::OpAssertFail => {
|
||||||
return Err(frame.error(ErrorKind::AssertionFailed));
|
return Err(frame.error(ErrorKind::AssertionFailed));
|
||||||
}
|
}
|
||||||
|
|
||||||
OpCode::OpCall => {
|
|
||||||
let callable = self.stack_pop();
|
|
||||||
self.call_value(frame.current_light_span(), Some(frame), callable)?;
|
|
||||||
|
|
||||||
// exit this loop and let the outer loop enter the new call
|
|
||||||
return Ok(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
OpCode::OpGetUpvalue(upv_idx) => {
|
|
||||||
let value = frame.upvalue(upv_idx).clone();
|
|
||||||
self.stack.push(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
OpCode::OpClosure(idx) => {
|
|
||||||
let blueprint = match &frame.chunk()[idx] {
|
|
||||||
Value::Blueprint(lambda) => lambda.clone(),
|
|
||||||
_ => panic!("compiler bug: non-blueprint in blueprint slot"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let upvalue_count = blueprint.upvalue_count;
|
|
||||||
debug_assert!(
|
|
||||||
upvalue_count > 0,
|
|
||||||
"OpClosure should not be called for plain lambdas"
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut upvalues = Upvalues::with_capacity(blueprint.upvalue_count);
|
|
||||||
self.populate_upvalues(&mut frame, upvalue_count, &mut upvalues)?;
|
|
||||||
self.stack
|
|
||||||
.push(Value::Closure(Rc::new(Closure::new_with_upvalues(
|
|
||||||
Rc::new(upvalues),
|
|
||||||
blueprint,
|
|
||||||
))));
|
|
||||||
}
|
|
||||||
|
|
||||||
OpCode::OpThunkSuspended(idx) | OpCode::OpThunkClosure(idx) => {
|
|
||||||
let blueprint = match &frame.chunk()[idx] {
|
|
||||||
Value::Blueprint(lambda) => lambda.clone(),
|
|
||||||
_ => panic!("compiler bug: non-blueprint in blueprint slot"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let upvalue_count = blueprint.upvalue_count;
|
|
||||||
let thunk = if matches!(op, OpCode::OpThunkClosure(_)) {
|
|
||||||
debug_assert!(
|
|
||||||
upvalue_count > 0,
|
|
||||||
"OpThunkClosure should not be called for plain lambdas"
|
|
||||||
);
|
|
||||||
Thunk::new_closure(blueprint)
|
|
||||||
} else {
|
|
||||||
Thunk::new_suspended(blueprint, frame.current_light_span())
|
|
||||||
};
|
|
||||||
let upvalues = thunk.upvalues_mut();
|
|
||||||
self.stack.push(Value::Thunk(thunk.clone()));
|
|
||||||
|
|
||||||
// From this point on we internally mutate the
|
|
||||||
// upvalues. The closure (if `is_closure`) is
|
|
||||||
// already in its stack slot, which means that it
|
|
||||||
// can capture itself as an upvalue for
|
|
||||||
// self-recursion.
|
|
||||||
self.populate_upvalues(&mut frame, upvalue_count, upvalues)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
OpCode::OpForce => {
|
|
||||||
if let Some(Value::Thunk(_)) = self.stack.last() {
|
|
||||||
let thunk = match self.stack_pop() {
|
|
||||||
Value::Thunk(t) => t,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let gen_span = frame.current_light_span();
|
|
||||||
|
|
||||||
self.push_call_frame(span, frame);
|
|
||||||
self.enqueue_generator("force", gen_span, |co| thunk.force(co));
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OpCode::OpFinalise(StackIdx(idx)) => {
|
|
||||||
match &self.stack[frame.stack_offset + idx] {
|
|
||||||
Value::Closure(_) => panic!("attempted to finalise a closure"),
|
|
||||||
Value::Thunk(thunk) => thunk.finalise(&self.stack[frame.stack_offset..]),
|
|
||||||
|
|
||||||
// In functions with "formals" attributes, it is
|
|
||||||
// possible for `OpFinalise` to be called on a
|
|
||||||
// non-capturing value, in which case it is a no-op.
|
|
||||||
//
|
|
||||||
// TODO: detect this in some phase and skip the finalise; fail here
|
|
||||||
_ => { /* TODO: panic here again to catch bugs */ }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Data-carrying operands should never be executed,
|
// Data-carrying operands should never be executed,
|
||||||
// that is a critical error in the VM/compiler.
|
// that is a critical error in the VM/compiler.
|
||||||
OpCode::DataStackIdx(_)
|
OpCode::DataStackIdx(_)
|
||||||
|
|
Loading…
Reference in a new issue