Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create a constant expression compiler/validator #298

Merged
merged 1 commit into from
Jan 23, 2024
Merged
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
116 changes: 116 additions & 0 deletions wrausmt-format/src/compiler/const_expression.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
use {
super::validation::{ModuleContext, ValidationType},
crate::{compiler::validation::KindResult, ValidationErrorKind},
wrausmt_common::true_or::TrueOr,
wrausmt_runtime::{
instructions::opcodes,
syntax::{
types::{NumType, RefType, ValueType},
CompiledExpr, Instruction, Operands, Resolved, UncompiledExpr,
},
},
};

macro_rules! instr {
($opcode:pat => $operands:pat) => {
Instruction {
opcode: $opcode,
operands: $operands,
..
}
};
}

/// A separate emitter/validater for constant expressions.
/// Rather than including branching logic in the primary emitter/validator, it's
/// much clearer to create a parallel implementation here.
///
/// There are some different validation conditions that don't apply to the main
/// validator (imported globals only, const globals only, ref funcs can be
/// updated).
pub fn compile_const_expr(
expr: &UncompiledExpr<Resolved>,
module: &ModuleContext,
expect_type: ValueType,
) -> KindResult<CompiledExpr> {
let mut out = Vec::<u8>::new();
let mut stack = Vec::<ValueType>::new();
for instr in &expr.instr {
validate_and_emit_instr(instr, &mut out, module, &mut stack)?;
}

(stack == [expect_type]).true_or(ValidationErrorKind::TypeMismatch {
actual: ValidationType::Value(*stack.first().unwrap_or(&ValueType::Void)),
expect: ValidationType::Value(expect_type),
})?;

Ok(CompiledExpr {
instr: out.into_boxed_slice(),
})
}

fn validate_and_emit_instr(
instr: &Instruction<Resolved>,
out: &mut Vec<u8>,
module: &ModuleContext,
stack: &mut Vec<ValueType>,
) -> KindResult<()> {
out.extend(instr.opcode.bytes());
match instr {
instr!(opcodes::I32_CONST => Operands::I32(v)) => {
stack.push(NumType::I32.into());

let bytes = &v.to_le_bytes()[..];
out.extend(bytes);
}
instr!(opcodes::I64_CONST => Operands::I64(v)) => {
stack.push(NumType::I64.into());

let bytes = &v.to_le_bytes()[..];
out.extend(bytes);
}
instr!(opcodes::F32_CONST => Operands::F32(v)) => {
stack.push(NumType::F32.into());

let bytes = &v.to_bits().to_le_bytes()[..];
out.extend(bytes);
}
instr!(opcodes::F64_CONST => Operands::F64(v)) => {
stack.push(NumType::F64.into());

let bytes = &v.to_bits().to_le_bytes()[..];
out.extend(bytes);
}
instr!(opcodes::REF_NULL => Operands::HeapType(ht)) => {
stack.push((*ht).into());

let htbyte = match ht {
RefType::Func => 0x70,
RefType::Extern => 0x6F,
};
out.push(htbyte);
}
instr!(opcodes::REF_FUNC => Operands::FuncIndex(fi)) => {
((fi.value() as usize) < module.funcs.len())
.true_or(ValidationErrorKind::UnknownFunc)?;
stack.push(RefType::Func.into());

let bytes = &fi.value().to_le_bytes()[..];
out.extend(bytes);
}
instr!(opcodes::GLOBAL_GET => Operands::GlobalIndex(gi)) => {
let global = module
.globals
.get(gi.value() as usize)
.ok_or(ValidationErrorKind::UnknownGlobal)?;
(global.imported && !global.globaltype.mutable)
.true_or(ValidationErrorKind::InvalidConstantGlobal)?;
stack.push(global.globaltype.valtype);

let bytes = &gi.value().to_le_bytes()[..];
out.extend(bytes);
}
_ => Err(ValidationErrorKind::InvalidConstantInstruction)?,
};
Ok(())
}
54 changes: 5 additions & 49 deletions wrausmt-format/src/compiler/emitter.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use {
super::{
validation::{ExpressionType, ModuleContext, Result, Validation, ValidationMode},
validation::{ModuleContext, Result, Validation, ValidationMode},
ToValidationError,
},
wrausmt_runtime::{
instructions::{op_consts, opcodes},
instructions::opcodes,
syntax::{
self,
location::Location,
Expand Down Expand Up @@ -214,13 +214,7 @@ impl<'a> ValidatingEmitter<'a> {

let resulttypes: Vec<_> = functype.results.clone();

let mut out = ValidatingEmitter::new(
validation_mode,
module,
localtypes,
resulttypes,
ExpressionType::Normal,
);
let mut out = ValidatingEmitter::new(validation_mode, module, localtypes, resulttypes);

out.emit_expr(&func.body)?;
out.emit_end(&func.location)?;
Expand All @@ -230,49 +224,15 @@ impl<'a> ValidatingEmitter<'a> {
})
}

/// Compile the body of the provided [`FuncField`] as if it were the
/// provided type. Instructions will be validated using the provided
/// [`ValidationMode`]. Validation uses the provided [`Module`] to
/// resolve module-wide indices. A final `END` opcode will not be
/// emitted.
pub fn simple_expression(
validation_mode: ValidationMode,
module: &ModuleContext,
expr: &UncompiledExpr<Resolved>,
resulttypes: Vec<ValueType>,
location: &Location,
expression_type: ExpressionType,
) -> Result<CompiledExpr> {
let mut out = ValidatingEmitter::new(
validation_mode,
module,
vec![],
resulttypes,
expression_type,
);
out.emit_expr(expr)?;
out.validate_end(location)?;
Ok(CompiledExpr {
instr: out.finish()?,
})
}

fn new(
validation_mode: ValidationMode,
module: &ModuleContext,
localtypes: Vec<ValueType>,
resulttypes: Vec<ValueType>,
expression_type: ExpressionType,
) -> ValidatingEmitter {
ValidatingEmitter {
output: Vec::new(),
validation: Validation::new(
validation_mode,
module,
localtypes,
resulttypes,
expression_type,
),
validation: Validation::new(validation_mode, module, localtypes, resulttypes),
}
}

Expand Down Expand Up @@ -320,11 +280,7 @@ impl<'a> Emitter for ValidatingEmitter<'a> {
}

fn emit_opcode(&mut self, opcode: Opcode) {
match opcode {
Opcode::Normal(o) => self.output.push(o),
Opcode::Extended(o) => self.output.extend(&[op_consts::EXTENDED_PREFIX, o]),
Opcode::Simd(o) => self.output.extend(&[op_consts::SIMD_PREFIX, o]),
}
self.output.extend(opcode.bytes());
}

fn len(&self) -> usize {
Expand Down
Loading
Loading