Model polymorphic stack explicitly
This commit is contained in:
parent
fdd527e996
commit
842ff25aaf
|
@ -26,6 +26,10 @@ pub struct BlockFrame {
|
||||||
pub end_position: usize,
|
pub end_position: usize,
|
||||||
/// A limit integer value, which is an index into the value stack indicating where to reset it to on a branch to that label.
|
/// A limit integer value, which is an index into the value stack indicating where to reset it to on a branch to that label.
|
||||||
pub value_stack_len: usize,
|
pub value_stack_len: usize,
|
||||||
|
/// Boolean which signals whether value stack became polymorphic. Value stack starts in non-polymorphic state and
|
||||||
|
/// becomes polymorphic only after an instruction that never passes control further is executed,
|
||||||
|
/// i.e. `unreachable`, `br` (but not `br_if`!), etc.
|
||||||
|
pub polymorphic_stack: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Type of block frame.
|
/// Type of block frame.
|
||||||
|
|
|
@ -1153,6 +1153,7 @@ impl FunctionContext {
|
||||||
branch_position: branch_position,
|
branch_position: branch_position,
|
||||||
end_position: end_position,
|
end_position: end_position,
|
||||||
value_stack_len: self.value_stack.len(),
|
value_stack_len: self.value_stack.len(),
|
||||||
|
polymorphic_stack: false,
|
||||||
}).map_err(|_| TrapKind::StackOverflow)?;
|
}).map_err(|_| TrapKind::StackOverflow)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -41,8 +41,6 @@ struct FunctionValidationContext<'a> {
|
||||||
enum StackValueType {
|
enum StackValueType {
|
||||||
/// Any value type.
|
/// Any value type.
|
||||||
Any,
|
Any,
|
||||||
/// Any number of any values of any type.
|
|
||||||
AnyUnlimited,
|
|
||||||
/// Concrete value type.
|
/// Concrete value type.
|
||||||
Specific(ValueType),
|
Specific(ValueType),
|
||||||
}
|
}
|
||||||
|
@ -347,13 +345,13 @@ impl Validator {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_drop(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
fn validate_drop(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
||||||
context.pop_any_value().map(|_| ())?;
|
context.pop_value(StackValueType::Any).map(|_| ())?;
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_select(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
fn validate_select(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
||||||
context.pop_value(ValueType::I32.into())?;
|
context.pop_value(ValueType::I32.into())?;
|
||||||
let select_type = context.pop_any_value()?;
|
let select_type = context.pop_value(StackValueType::Any)?;
|
||||||
context.pop_value(select_type)?;
|
context.pop_value(select_type)?;
|
||||||
context.push_value(select_type)?;
|
context.push_value(select_type)?;
|
||||||
Ok(InstructionOutcome::ValidateNextInstruction)
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
@ -367,7 +365,7 @@ impl Validator {
|
||||||
|
|
||||||
fn validate_set_local(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
|
fn validate_set_local(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
|
||||||
let local_type = context.require_local(index)?;
|
let local_type = context.require_local(index)?;
|
||||||
let value_type = context.pop_any_value()?;
|
let value_type = context.pop_value(StackValueType::Any)?;
|
||||||
if local_type != value_type {
|
if local_type != value_type {
|
||||||
return Err(Error(format!("Trying to update local {} of type {:?} with value of type {:?}", index, local_type, value_type)));
|
return Err(Error(format!("Trying to update local {} of type {:?} with value of type {:?}", index, local_type, value_type)));
|
||||||
}
|
}
|
||||||
|
@ -376,7 +374,7 @@ impl Validator {
|
||||||
|
|
||||||
fn validate_tee_local(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
|
fn validate_tee_local(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
|
||||||
let local_type = context.require_local(index)?;
|
let local_type = context.require_local(index)?;
|
||||||
let value_type = context.tee_any_value()?;
|
let value_type = context.tee_value(StackValueType::Any)?;
|
||||||
if local_type != value_type {
|
if local_type != value_type {
|
||||||
return Err(Error(format!("Trying to update local {} of type {:?} with value of type {:?}", index, local_type, value_type)));
|
return Err(Error(format!("Trying to update local {} of type {:?} with value of type {:?}", index, local_type, value_type)));
|
||||||
}
|
}
|
||||||
|
@ -397,7 +395,7 @@ impl Validator {
|
||||||
let global = context.module.require_global(index, Some(true))?;
|
let global = context.module.require_global(index, Some(true))?;
|
||||||
global.content_type().into()
|
global.content_type().into()
|
||||||
};
|
};
|
||||||
let value_type = context.pop_any_value()?;
|
let value_type = context.pop_value(StackValueType::Any)?;
|
||||||
if global_type != value_type {
|
if global_type != value_type {
|
||||||
return Err(Error(format!("Trying to update global {} of type {:?} with value of type {:?}", index, global_type, value_type)));
|
return Err(Error(format!("Trying to update global {} of type {:?} with value of type {:?}", index, global_type, value_type)));
|
||||||
}
|
}
|
||||||
|
@ -610,47 +608,50 @@ impl<'a> FunctionValidationContext<'a> {
|
||||||
Ok(self.value_stack.push(value_type.into())?)
|
Ok(self.value_stack.push(value_type.into())?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pop_value(&mut self, value_type: StackValueType) -> Result<(), Error> {
|
fn pop_value(&mut self, value_type: StackValueType) -> Result<StackValueType, Error> {
|
||||||
self.check_stack_access()?;
|
let (is_stack_polymorphic, label_value_stack_len) = {
|
||||||
match self.value_stack.pop()? {
|
let frame = self.top_label()?;
|
||||||
StackValueType::Specific(stack_value_type) if stack_value_type == value_type => Ok(()),
|
(frame.polymorphic_stack, frame.value_stack_len)
|
||||||
StackValueType::Any => Ok(()),
|
};
|
||||||
StackValueType::AnyUnlimited => {
|
let stack_is_empty = self.value_stack.len() == label_value_stack_len;
|
||||||
self.value_stack.push(StackValueType::AnyUnlimited)?;
|
let actual_value = if stack_is_empty && is_stack_polymorphic {
|
||||||
Ok(())
|
StackValueType::Any
|
||||||
},
|
} else {
|
||||||
stack_value_type @ _ => Err(Error(format!("Expected value of type {:?} on top of stack. Got {:?}", value_type, stack_value_type))),
|
self.check_stack_access()?;
|
||||||
|
self.value_stack.pop()?
|
||||||
|
};
|
||||||
|
match actual_value {
|
||||||
|
StackValueType::Specific(stack_value_type) if stack_value_type == value_type => {
|
||||||
|
Ok(actual_value)
|
||||||
|
}
|
||||||
|
StackValueType::Any => Ok(actual_value),
|
||||||
|
stack_value_type @ _ => Err(Error(format!(
|
||||||
|
"Expected value of type {:?} on top of stack. Got {:?}",
|
||||||
|
value_type, stack_value_type
|
||||||
|
))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tee_value(&mut self, value_type: StackValueType) -> Result<(), Error> {
|
fn check_stack_access(&self) -> Result<(), Error> {
|
||||||
self.check_stack_access()?;
|
let value_stack_min = self.frame_stack.top().expect("at least 1 topmost block").value_stack_len;
|
||||||
match *self.value_stack.top()? {
|
if self.value_stack.len() > value_stack_min {
|
||||||
StackValueType::Specific(stack_value_type) if stack_value_type == value_type => Ok(()),
|
Ok(())
|
||||||
StackValueType::Any | StackValueType::AnyUnlimited => Ok(()),
|
} else {
|
||||||
stack_value_type @ _ => Err(Error(format!("Expected value of type {:?} on top of stack. Got {:?}", value_type, stack_value_type))),
|
Err(Error("Trying to access parent frame stack values.".into()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pop_any_value(&mut self) -> Result<StackValueType, Error> {
|
fn tee_value(&mut self, value_type: StackValueType) -> Result<StackValueType, Error> {
|
||||||
self.check_stack_access()?;
|
let value = self.pop_value(value_type)?;
|
||||||
match self.value_stack.pop()? {
|
self.push_value(value)?;
|
||||||
StackValueType::Specific(stack_value_type) => Ok(StackValueType::Specific(stack_value_type)),
|
Ok(value)
|
||||||
StackValueType::Any => Ok(StackValueType::Any),
|
|
||||||
StackValueType::AnyUnlimited => {
|
|
||||||
self.value_stack.push(StackValueType::AnyUnlimited)?;
|
|
||||||
Ok(StackValueType::Any)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tee_any_value(&mut self) -> Result<StackValueType, Error> {
|
|
||||||
self.check_stack_access()?;
|
|
||||||
Ok(self.value_stack.top().map(Clone::clone)?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unreachable(&mut self) -> Result<(), Error> {
|
fn unreachable(&mut self) -> Result<(), Error> {
|
||||||
Ok(self.value_stack.push(StackValueType::AnyUnlimited)?)
|
let frame = self.frame_stack.top_mut()?;
|
||||||
|
self.value_stack.resize(frame.value_stack_len, StackValueType::Any);
|
||||||
|
frame.polymorphic_stack = true;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn top_label(&self) -> Result<&BlockFrame, Error> {
|
fn top_label(&self) -> Result<&BlockFrame, Error> {
|
||||||
|
@ -665,23 +666,31 @@ impl<'a> FunctionValidationContext<'a> {
|
||||||
branch_position: self.position,
|
branch_position: self.position,
|
||||||
end_position: self.position,
|
end_position: self.position,
|
||||||
value_stack_len: self.value_stack.len(),
|
value_stack_len: self.value_stack.len(),
|
||||||
|
polymorphic_stack: false,
|
||||||
})?)
|
})?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pop_label(&mut self) -> Result<InstructionOutcome, Error> {
|
fn pop_label(&mut self) -> Result<InstructionOutcome, Error> {
|
||||||
let frame = self.frame_stack.pop()?;
|
// Don't pop frame yet. This is essential since we still might pop values from the value stack
|
||||||
let actual_value_type = if self.value_stack.len() > frame.value_stack_len {
|
// and this in turn requires current frame to check whether or not we've reached
|
||||||
Some(self.value_stack.pop()?)
|
// unreachable.
|
||||||
} else {
|
let block_type = self.frame_stack.top()?.block_type;
|
||||||
None
|
match block_type {
|
||||||
};
|
BlockType::NoResult => (),
|
||||||
self.value_stack.resize(frame.value_stack_len, StackValueType::Any);
|
BlockType::Value(required_value_type) => {
|
||||||
|
self.pop_value(StackValueType::Specific(required_value_type))?;
|
||||||
match frame.block_type {
|
}
|
||||||
BlockType::NoResult if actual_value_type.map(|vt| vt.is_any_unlimited()).unwrap_or(true) => (),
|
|
||||||
BlockType::Value(required_value_type) if actual_value_type.map(|vt| vt == required_value_type).unwrap_or(false) => (),
|
|
||||||
_ => return Err(Error(format!("Expected block to return {:?} while it has returned {:?}", frame.block_type, actual_value_type))),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let frame = self.frame_stack.pop()?;
|
||||||
|
if self.value_stack.len() != frame.value_stack_len {
|
||||||
|
return Err(Error(format!(
|
||||||
|
"Unexpected stack height {}, expected {}",
|
||||||
|
self.value_stack.len(),
|
||||||
|
frame.value_stack_len
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
if !self.frame_stack.is_empty() {
|
if !self.frame_stack.is_empty() {
|
||||||
self.labels.insert(frame.begin_position, self.position);
|
self.labels.insert(frame.begin_position, self.position);
|
||||||
}
|
}
|
||||||
|
@ -707,15 +716,6 @@ impl<'a> FunctionValidationContext<'a> {
|
||||||
.ok_or(Error(format!("Trying to access local with index {} when there are only {} locals", idx, self.locals.len())))
|
.ok_or(Error(format!("Trying to access local with index {} when there are only {} locals", idx, self.locals.len())))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_stack_access(&self) -> Result<(), Error> {
|
|
||||||
let value_stack_min = self.frame_stack.top().expect("at least 1 topmost block").value_stack_len;
|
|
||||||
if self.value_stack.len() > value_stack_min {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(Error("Trying to access parent frame stack values.".into()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_labels(self) -> HashMap<usize, usize> {
|
fn into_labels(self) -> HashMap<usize, usize> {
|
||||||
self.labels
|
self.labels
|
||||||
}
|
}
|
||||||
|
@ -729,16 +729,9 @@ impl StackValueType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_any_unlimited(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
&StackValueType::AnyUnlimited => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn value_type(&self) -> ValueType {
|
fn value_type(&self) -> ValueType {
|
||||||
match self {
|
match self {
|
||||||
&StackValueType::Any | &StackValueType::AnyUnlimited => unreachable!("must be checked by caller"),
|
&StackValueType::Any => unreachable!("must be checked by caller"),
|
||||||
&StackValueType::Specific(value_type) => value_type,
|
&StackValueType::Specific(value_type) => value_type,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -752,7 +745,7 @@ impl From<ValueType> for StackValueType {
|
||||||
|
|
||||||
impl PartialEq<StackValueType> for StackValueType {
|
impl PartialEq<StackValueType> for StackValueType {
|
||||||
fn eq(&self, other: &StackValueType) -> bool {
|
fn eq(&self, other: &StackValueType) -> bool {
|
||||||
if self.is_any() || other.is_any() || self.is_any_unlimited() || other.is_any_unlimited() {
|
if self.is_any() || other.is_any() {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
self.value_type() == other.value_type()
|
self.value_type() == other.value_type()
|
||||||
|
@ -762,7 +755,7 @@ impl PartialEq<StackValueType> for StackValueType {
|
||||||
|
|
||||||
impl PartialEq<ValueType> for StackValueType {
|
impl PartialEq<ValueType> for StackValueType {
|
||||||
fn eq(&self, other: &ValueType) -> bool {
|
fn eq(&self, other: &ValueType) -> bool {
|
||||||
if self.is_any() || self.is_any_unlimited() {
|
if self.is_any() {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
self.value_type() == *other
|
self.value_type() == *other
|
||||||
|
|
Loading…
Reference in New Issue