interpreter: initial typesystem support

This commit is contained in:
Guillem L. Jara
2026-05-25 12:36:57 +02:00
parent 98fa3c33c2
commit d30e5d33fc
5 changed files with 220 additions and 71 deletions
+28 -2
View File
@@ -16,6 +16,8 @@ use std::fmt::{Debug, Display};
pub use lower::test_interpreter;
use crate::ir::lower::ValueContext;
#[derive(Clone, Copy, Debug)]
#[repr(transparent)]
pub struct NonLocal(pub u16);
@@ -140,13 +142,13 @@ impl Instruction {
}
}
fn load_store(opcode: impl Into<OpCode>, dest: Reg, src: NonLocal) -> Self {
fn load_store(opcode: impl Into<OpCode>, dest: Reg, src: NonLocal, ctx: ValueContext) -> Self {
let opcode = opcode.into();
debug_assert!(opcode.is_load_store());
Self {
opcode,
args: Arguments { load_store: (dest, src) },
hint: Hint::None,
hint: ctx.into(),
}
}
@@ -252,6 +254,8 @@ pub enum Hint {
UnboxedFloat64,
UnboxedLhsFloat64,
UnboxedRhsFloat64,
ScalarCtx,
ArrayCtx,
}
trait HintedReg {
@@ -259,6 +263,26 @@ trait HintedReg {
fn hint(&self) -> Hint;
}
impl From<ValueContext> for Hint {
fn from(value: ValueContext) -> Self {
match value {
ValueContext::Scalar => Self::ScalarCtx,
ValueContext::Array => Self::ArrayCtx,
ValueContext::Untyped => Self::None,
}
}
}
impl From<Hint> for ValueContext {
fn from(value: Hint) -> Self {
match value {
Hint::ScalarCtx => Self::Scalar,
Hint::ArrayCtx => Self::Array,
_ => Self::Untyped,
}
}
}
impl Debug for Instruction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Instruction::{:?}", self.opcode)?;
@@ -346,6 +370,8 @@ impl Display for Instruction {
Hint::UnboxedFloat64 => write!(f, " @ all_unboxedf64"),
Hint::UnboxedLhsFloat64 => write!(f, " @ lhs_unboxedf64"),
Hint::UnboxedRhsFloat64 => write!(f, " @ rhs_unboxedf64"),
Hint::ScalarCtx => write!(f, "@ scalar_ctx"),
Hint::ArrayCtx => write!(f, "@ array_ctx"),
Hint::None => Ok(()),
}
}
+37 -29
View File
@@ -3,7 +3,7 @@
// For the full copyright and license information, please view the LICENSE
// files that was distributed with this source code.
use std::{hash::Hash, mem::forget};
use std::mem::forget;
use bumpalo::{Bump, collections::Vec};
use parser::{
@@ -13,7 +13,8 @@ use parser::{
use crate::{
ir::{Hint, HintedReg, Instruction, Label, NonLocal, OpCode, Reg},
vm::{Consts, ExecMode, Interpreter, SymbolTable, Value},
types::Value,
vm::{Consts, ExecMode, Interpreter, SymbolTable},
};
#[derive(Debug)]
@@ -30,6 +31,13 @@ pub struct Code<'arena> {
#[derive(Debug)]
struct LinearReg(Reg, Hint);
#[derive(Clone, Copy)]
pub enum ValueContext {
Untyped,
Scalar,
Array,
}
impl Code<'_> {
fn lower_body(&mut self, body: &Body) {
for stmnt in &body.0 {
@@ -41,7 +49,7 @@ impl Code<'_> {
match stmnt {
Statement::If { condition, then_body, else_body } => {
let mut state = RegsState::new(self);
let condition = self.lower_expr(condition);
let condition = self.lower_expr(condition, ValueContext::Scalar);
let label_then = self.following_instr(1);
let if_label =
self.bc
@@ -60,7 +68,7 @@ impl Code<'_> {
}
Statement::While { condition, then_body } => {
let cond_label = self.following_instr(0);
let condition = self.lower_expr(condition);
let condition = self.lower_expr(condition, ValueContext::Scalar);
let while_label = self.bc.emit(Instruction::branch(
condition.reg(),
self.following_instr(1),
@@ -74,7 +82,7 @@ impl Code<'_> {
Statement::DoWhile { then_body, condition } => {
let do_label = self.following_instr(0);
self.lower_body(then_body);
let condition = self.lower_expr(condition);
let condition = self.lower_expr(condition, ValueContext::Scalar);
self.bc.emit(Instruction::branch(
condition.reg(),
do_label,
@@ -83,75 +91,83 @@ impl Code<'_> {
self.free_reg(condition);
}
Statement::Simple(SimpleStatement::Expression(expr)) => {
let reg = self.lower_expr(expr);
let reg = self.lower_expr(expr, ValueContext::Untyped);
self.free_reg(reg);
}
_ => todo!(),
}
}
fn lower_expr(&mut self, expr: &Expr) -> LinearReg {
fn lower_expr(&mut self, expr: &Expr, ctx: ValueContext) -> LinearReg {
let mut dest = self.alloc_reg();
let hint = self.lower_expr_into(expr, dest.reg());
let hint = self.lower_expr_into(expr, dest.reg(), ctx);
dest.1 = hint;
dest
}
fn lower_expr_into(&mut self, expr: &Expr, dest: Reg) -> Hint {
fn lower_expr_into(&mut self, expr: &Expr, dest: Reg, ctx: ValueContext) -> Hint {
match expr {
Expr::Leaf(atom) => match atom {
Atom::Variable(Variable::User(ident)) => {
let src = self.symbols.register_user_var(ident, self.arena);
self.bc
.emit(Instruction::load_store(OpCode::LoadUser, dest, src));
.emit(Instruction::load_store(OpCode::LoadUser, dest, src, ctx));
}
&Atom::Number(n) => {
let src = self.register_const(Value(n));
self.bc
.emit(Instruction::load_store(OpCode::LoadConst, dest, src));
let src = self.register_const(Value::Float(n));
self.bc.emit(Instruction::load_store(
OpCode::LoadConst,
dest,
src,
ValueContext::Scalar,
));
return Hint::UnboxedFloat64;
}
_ => todo!(),
},
Expr::Node(node) => match node.as_ref() {
ExprNode::UnaryOperation(op, expr) => {
let src = self.lower_expr(expr);
let src = self.lower_expr(expr, ValueContext::Scalar);
self.bc.emit(Instruction::unary(*op, dest, &src));
self.free_reg(src);
}
ExprNode::BinaryOperation(op, lhs, rhs) => {
let lhs = self.lower_expr(lhs);
let rhs = self.lower_expr(rhs);
let lhs = self.lower_expr(lhs, ValueContext::Scalar);
let rhs = self.lower_expr(rhs, ValueContext::Scalar);
self.bc.emit(Instruction::binary(*op, dest, &lhs, &rhs));
self.free_reg(lhs);
self.free_reg(rhs);
}
ExprNode::Ternary(cond, true_then, false_then) => {
self.lower_expr_into(cond, dest);
self.lower_expr_into(cond, dest, ValueContext::Scalar);
let mut state = RegsState::new(self);
let branch =
self.bc
.emit(Instruction::branch(dest, self.following_instr(1), Label(0)));
state = state.scope(self, |c| c.lower_expr_into(true_then, dest));
state = state.scope(self, |c| {
c.lower_expr_into(true_then, dest, ValueContext::Scalar)
});
let jump = self.bc.emit(Instruction::jump(Label(0)));
let label = self.following_instr(0);
state.scope_hwm(self, |c| c.lower_expr_into(false_then, dest));
state.scope_hwm(self, |c| {
c.lower_expr_into(false_then, dest, ValueContext::Scalar)
});
self.bc.nth(branch).args.branch.2 = label;
self.bc.nth(jump).args.jump = self.following_instr(0);
}
ExprNode::BinaryPlaceOperation(BinaryPlaceOperator::Assignment, place, expr) => {
self.lower_expr_into(expr, dest);
self.lower_expr_into(expr, dest, ctx);
let Place::Variable(Variable::User(var)) = place else {
todo!()
};
let var = self.symbols.register_user_var(var, self.arena);
self.bc
.emit(Instruction::load_store(OpCode::StoreUser, dest, var));
.emit(Instruction::load_store(OpCode::StoreUser, dest, var, ctx));
}
_ => todo!(),
},
@@ -286,14 +302,6 @@ impl From<BinaryOperator> for OpCode {
}
}
impl Hash for Value {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
state.write(&self.0.to_ne_bytes());
}
}
impl Eq for Value {}
// trait Fold<T> {
// type Args;
// fn fold(&self, args: Self::Args) -> T;
+1
View File
@@ -6,6 +6,7 @@
#![allow(dead_code)]
pub(crate) mod ir;
mod types;
mod vm;
pub use ir::test_interpreter;
+104 -9
View File
@@ -1,14 +1,109 @@
use std::ptr::NonNull;
use std::{
hash::Hash,
mem::discriminant,
ops::{Add, Div, Mul, Sub},
};
// / NaN-boxed value
union AValue {
float: f64,
ptr: NonNull<()>,
use ahash::RandomState;
use hashbrown::HashMap;
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
Float(f64),
String(Vec<u8>),
Array(HashMap<String, Self, RandomState>),
Bool(bool),
Untyped,
Unassigned,
}
impl AValue {
fn get_float(&self) -> f64 {
let float = unsafe { self.float };
if !float.is_nan() { float } else { todo!() }
impl Value {
/// Called when loading a variable's value. Forces subsequent uses to be
/// typed as an AWK scalar (anything that's not an array, basically).
pub fn scalar_context(&mut self) -> &mut Self {
// TODO: Exit "nicely" on Self::Array(_).
match self {
Self::Untyped => *self = Self::Unassigned,
Self::Array(_) => panic!("Attempted to use array in scalar context!"),
_ => {}
}
self
}
pub fn array_context(&mut self) -> &mut Self {
// TODO: Exit "nicely" on Self::Array(_).
match self {
Self::Untyped => *self = Self::Array(HashMap::with_hasher(RandomState::new())),
Self::Array(_) => {}
_ => panic!("Attempted to use scalar as array!"),
}
self
}
pub fn to_bool(&self) -> bool {
match self {
&Self::Float(f) => f != 0.,
&Self::Bool(b) => b,
Self::String(str) => !str.is_empty(),
_ => false,
}
}
fn to_num(&self) -> f64 {
match self {
&Self::Float(f) => f,
&Self::Bool(b) => b as usize as f64,
Self::String(s) => str::from_utf8(s)
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(0.),
_ => 0.,
}
}
}
impl Add for &'_ Value {
type Output = Value;
fn add(self, rhs: Self) -> Self::Output {
Value::Float(self.to_num() + rhs.to_num())
}
}
impl Sub for &'_ Value {
type Output = Value;
fn sub(self, rhs: Self) -> Self::Output {
Value::Float(self.to_num() - rhs.to_num())
}
}
impl Mul for &'_ Value {
type Output = Value;
fn mul(self, rhs: Self) -> Self::Output {
Value::Float(self.to_num() * rhs.to_num())
}
}
impl Div for &'_ Value {
type Output = Value;
fn div(self, rhs: Self) -> Self::Output {
// TODO: panic "nicely" on div by zero.
Value::Float(self.to_num() / rhs.to_num())
}
}
impl Eq for Value {}
impl Hash for Value {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
discriminant(self).hash(state);
match self {
Self::Float(f) => state.write_u64(f.to_bits()),
Self::String(s) => s.hash(state),
Self::Bool(b) => b.hash(state),
_ => {}
}
}
}
+50 -31
View File
@@ -1,4 +1,7 @@
use std::fmt::{self, Display};
use std::{
fmt::{self, Display},
mem::replace,
};
use ahash::RandomState;
use bumpalo::{Bump, collections::Vec};
@@ -6,15 +9,14 @@ use hashbrown::HashMap;
use indexmap_allocator_api::{IndexMap, IndexSet};
use parser::Identifier;
use crate::ir::{
NonLocal, OpCode, Reg,
lower::{Bytecode, Code},
use crate::{
ir::{
NonLocal, OpCode, Reg,
lower::{Bytecode, Code, ValueContext},
},
types::Value,
};
#[derive(Clone, Debug, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct Value(pub f64); // TODO: use NaN-boxing.
#[derive(Debug)]
pub enum ExecMode {
Uu,
@@ -53,7 +55,7 @@ impl<'a> Interpreter<'a> {
arena: code.arena,
bc: code.bc,
program_counter: 0,
registers: Registers(bumpalo::vec![in code.arena; Value(0.); 8]),
registers: Registers(bumpalo::vec![in code.arena; Value::Untyped; 8]),
symbols: code.symbols,
consts: code.consts,
compat,
@@ -68,12 +70,18 @@ impl<'a> SymbolTable<'a> {
records: HashMap::with_hasher_in(RandomState::new(), arena),
}
}
fn lookup_user_var(&self, var: NonLocal) -> &Value {
self.user.get_index(var.0 as _).unwrap().1
fn lookup_user_var(&mut self, var: NonLocal, ctx: ValueContext) -> &Value {
let v = self.user.get_index_mut(var.0 as _).unwrap().1;
match ctx {
ValueContext::Untyped => v,
ValueContext::Scalar => v.scalar_context(),
ValueContext::Array => v.array_context(),
}
}
fn write_user_val(&mut self, var: NonLocal, value: &Value) {
*self.user.get_index_mut(var.0 as _).unwrap().1 = Value::clone(value);
fn write_user_val(&mut self, var: NonLocal, value: Value) {
*self.user.get_index_mut(var.0 as _).unwrap().1 = value;
}
pub fn register_user_var(&mut self, var: &Identifier, bump: &'a Bump) -> NonLocal {
@@ -84,7 +92,7 @@ impl<'a> SymbolTable<'a> {
namespace: bump.alloc_str(var.namespace),
literal: bump.alloc_str(var.literal),
};
NonLocal(self.user.insert_full(ident, Value(0.)).0 as _)
NonLocal(self.user.insert_full(ident, Value::Untyped).0 as _)
}
}
}
@@ -101,37 +109,41 @@ impl Interpreter<'_> {
match instr {
// ix if let Some(&(dest, src)) = ix.get_unary() => {}
ix if let Some(&(dest, lhs, rhs)) = ix.get_binary() => {
let lhs = self.registers.read(lhs);
let rhs = self.registers.read(rhs);
let lhs = self.registers.get(lhs);
let rhs = self.registers.get(rhs);
let val = match ix.opcode {
OpCode::Add => Value(lhs.0 + rhs.0),
OpCode::Subtract => Value(lhs.0 - rhs.0),
OpCode::Multiply => Value(lhs.0 * rhs.0),
OpCode::Divide => Value(lhs.0 / rhs.0),
OpCode::Add => lhs + rhs,
OpCode::Subtract => lhs - rhs,
OpCode::Multiply => lhs * rhs,
OpCode::Divide => lhs / rhs,
_ => todo!(),
};
self.registers.write(dest, &val);
self.registers.write(dest, val);
}
ix if let Some(&(dest, src)) = ix.get_load_store() => match ix.opcode {
OpCode::LoadConst => self
.registers
.write(dest, self.consts.0.get_index(src.0 as _).unwrap()),
.write(dest, self.consts.0.get_index(src.0 as _).unwrap().clone()),
OpCode::LoadUser => {
self.registers
.write(dest, self.symbols.lookup_user_var(src));
self.registers.write(
dest,
self.symbols.lookup_user_var(src, ix.hint.into()).clone(),
);
}
OpCode::StoreUser => {
self.symbols.write_user_val(src, self.registers.read(dest));
self.symbols
.write_user_val(src, self.registers.get(dest).clone());
}
_ => todo!(),
},
ix if let Some((cond, true_to, false_to)) = ix.get_branch() => {
let label = if self.registers.read(*cond).0 == 0. {
false_to.0
} else {
let label = if self.registers.get(*cond).to_bool() {
true_to.0
} else {
false_to.0
};
self.program_counter = label as _;
continue;
}
ix if let Some(label) = ix.get_jump() => {
@@ -146,11 +158,18 @@ impl Interpreter<'_> {
}
impl Registers<'_> {
fn read(&self, src: Reg) -> &Value {
fn replace(&mut self, src: Reg, f: impl FnOnce(Value) -> Value) {
let val = replace(self.get_mut(src), Value::Untyped);
self.write(src, f(val));
}
fn get(&self, src: Reg) -> &Value {
&self.0[src.0 as usize]
}
fn write(&mut self, dest: Reg, src: &Value) {
self.0[dest.0 as usize] = Value::clone(src);
fn get_mut(&mut self, src: Reg) -> &mut Value {
&mut self.0[src.0 as usize]
}
fn write(&mut self, dest: Reg, src: Value) {
self.0[dest.0 as usize] = src;
}
}