mirror of
https://github.com/uutils/awk.git
synced 2026-06-10 16:15:04 -07:00
interpreter: initial vm
This commit is contained in:
Generated
+1
@@ -504,6 +504,7 @@ dependencies = [
|
||||
"bumpalo",
|
||||
"color-eyre",
|
||||
"either",
|
||||
"hashbrown 0.17.1",
|
||||
"indexmap",
|
||||
"memchr",
|
||||
"parser",
|
||||
|
||||
@@ -13,6 +13,7 @@ tracing.workspace = true
|
||||
parser.workspace = true
|
||||
bumpalo.workspace = true
|
||||
allocator-api2.workspace = true
|
||||
hashbrown.workspace = true
|
||||
indexmap = "2.14.0"
|
||||
|
||||
[lints]
|
||||
|
||||
+74
-53
@@ -10,9 +10,7 @@
|
||||
//! emitting bytecode without an intermediate AST because AWK contextual
|
||||
//! shenanigans; _even_ if it was possible, good luck maintaining that.
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
mod lower;
|
||||
pub mod lower;
|
||||
|
||||
pub use lower::test_interpreter;
|
||||
|
||||
@@ -20,23 +18,23 @@ use std::fmt::{Debug, Display};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(transparent)]
|
||||
struct NonLocal(u16);
|
||||
pub struct NonLocal(pub u16);
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
struct Reg(u16);
|
||||
pub struct Reg(pub u16);
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(transparent)]
|
||||
struct Label(u16);
|
||||
pub struct Label(u16);
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(transparent)]
|
||||
struct ArgCount(u16);
|
||||
pub struct ArgCount(u16);
|
||||
|
||||
#[repr(u8, align(1))]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum OpCode {
|
||||
pub enum OpCode {
|
||||
// Unary operations
|
||||
Record,
|
||||
Negation,
|
||||
@@ -63,10 +61,12 @@ enum OpCode {
|
||||
Modulo,
|
||||
|
||||
// Intrinsic operations
|
||||
Load,
|
||||
LoadUser,
|
||||
LoadBultin,
|
||||
LoadConst,
|
||||
Copy,
|
||||
Store,
|
||||
StoreUser,
|
||||
StoreBuiltin,
|
||||
IntrinsicCall,
|
||||
UserCall,
|
||||
IndirectCall,
|
||||
@@ -80,26 +80,36 @@ const _: () = const { assert!(size_of::<Instruction>() <= 8) };
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C, align(8))]
|
||||
struct Instruction {
|
||||
opcode: OpCode,
|
||||
hint: Hint,
|
||||
args: Arguments,
|
||||
pub struct Instruction {
|
||||
pub opcode: OpCode,
|
||||
pub hint: Hint,
|
||||
pub args: Arguments,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C, align(2))]
|
||||
union Arguments {
|
||||
unary_local: (Reg, Reg),
|
||||
binary_local: (Reg, Reg, Reg),
|
||||
load_store: (Reg, NonLocal),
|
||||
jump: Label,
|
||||
ret: Reg,
|
||||
branch: (Reg, Label, Label),
|
||||
br_if: (Reg, Label),
|
||||
call: (Reg, NonLocal, ArgCount),
|
||||
ind_call: (Reg, Reg, ArgCount),
|
||||
pub union Arguments {
|
||||
unary_local: UnaryArg,
|
||||
binary_local: BinaryArg,
|
||||
load_store: LoadStoreArg,
|
||||
jump: JumpArg,
|
||||
ret: RetArg,
|
||||
branch: BranchArg,
|
||||
br_if: BrIfArg,
|
||||
call: CallArgs,
|
||||
ind_call: IndCallArgs,
|
||||
}
|
||||
|
||||
pub type UnaryArg = (Reg, Reg);
|
||||
pub type BinaryArg = (Reg, Reg, Reg);
|
||||
pub type LoadStoreArg = (Reg, NonLocal);
|
||||
pub type JumpArg = Label;
|
||||
pub type RetArg = Reg;
|
||||
pub type BranchArg = (Reg, Label, Label);
|
||||
pub type BrIfArg = (Reg, Label);
|
||||
pub type CallArgs = (Reg, NonLocal, ArgCount);
|
||||
pub type IndCallArgs = (Reg, Reg, ArgCount);
|
||||
|
||||
impl Instruction {
|
||||
fn unary(opcode: impl Into<OpCode>, dest: Reg, src: &impl HintedReg) -> Self {
|
||||
let opcode = opcode.into();
|
||||
@@ -180,6 +190,24 @@ impl Instruction {
|
||||
hint: Hint::None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_unary(&self) -> Option<&UnaryArg> {
|
||||
self.opcode
|
||||
.is_unary()
|
||||
.then(|| unsafe { &self.args.unary_local })
|
||||
}
|
||||
|
||||
pub fn get_binary(&self) -> Option<&BinaryArg> {
|
||||
self.opcode
|
||||
.is_binary()
|
||||
.then(|| unsafe { &self.args.binary_local })
|
||||
}
|
||||
|
||||
pub fn get_load_store(&self) -> Option<&LoadStoreArg> {
|
||||
self.opcode
|
||||
.is_load_store()
|
||||
.then(|| unsafe { &self.args.load_store })
|
||||
}
|
||||
}
|
||||
|
||||
impl OpCode {
|
||||
@@ -213,7 +241,14 @@ impl OpCode {
|
||||
}
|
||||
|
||||
fn is_load_store(self) -> bool {
|
||||
matches!(self, Self::Load | Self::Store | Self::LoadConst)
|
||||
matches!(
|
||||
self,
|
||||
Self::LoadUser
|
||||
| Self::LoadBultin
|
||||
| Self::LoadConst
|
||||
| Self::StoreUser
|
||||
| Self::StoreBuiltin
|
||||
)
|
||||
}
|
||||
|
||||
fn is_jump(self) -> bool {
|
||||
@@ -231,7 +266,7 @@ impl OpCode {
|
||||
|
||||
#[repr(u8, align(1))]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum Hint {
|
||||
pub enum Hint {
|
||||
None = 0,
|
||||
UnboxedFloat64,
|
||||
UnboxedLhsFloat64,
|
||||
@@ -247,35 +282,15 @@ impl Debug for Instruction {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Instruction::{:?}", self.opcode)?;
|
||||
match self.opcode {
|
||||
OpCode::Record
|
||||
| OpCode::Negation
|
||||
| OpCode::ToInt
|
||||
| OpCode::Negative
|
||||
| OpCode::Concat
|
||||
| OpCode::Copy => {
|
||||
op if op.is_unary() => {
|
||||
let (dest, data) = unsafe { &self.args.unary_local };
|
||||
write!(f, "({dest:?}, {data:?})")
|
||||
}
|
||||
OpCode::Eq
|
||||
| OpCode::NEq
|
||||
| OpCode::Gt
|
||||
| OpCode::Lt
|
||||
| OpCode::LtE
|
||||
| OpCode::GtE
|
||||
| OpCode::And
|
||||
| OpCode::Or
|
||||
| OpCode::Matches
|
||||
| OpCode::MatchesNot
|
||||
| OpCode::Add
|
||||
| OpCode::Subtract
|
||||
| OpCode::Multiply
|
||||
| OpCode::Divide
|
||||
| OpCode::Raise
|
||||
| OpCode::Modulo => {
|
||||
op if op.is_binary() => {
|
||||
let (dest, lhs, rhs) = unsafe { &self.args.binary_local };
|
||||
write!(f, "({dest:?}, {lhs:?}, {rhs:?})")
|
||||
}
|
||||
OpCode::Load | OpCode::LoadConst | OpCode::Store => {
|
||||
op if op.is_load_store() => {
|
||||
let (dest, src) = unsafe { &self.args.load_store };
|
||||
write!(f, "({dest:?}, {src:?})")
|
||||
}
|
||||
@@ -323,14 +338,18 @@ impl Display for Instruction {
|
||||
let (dest, lhs, rhs) = unsafe { &self.args.binary_local };
|
||||
write!(f, "{dest} <- {op} {lhs}, {rhs}")
|
||||
}
|
||||
op @ (OpCode::Load | OpCode::Store) => {
|
||||
op @ (OpCode::LoadUser | OpCode::StoreUser) => {
|
||||
let (dest, src) = unsafe { &self.args.load_store };
|
||||
write!(f, "{dest} <- {op} global[{src}]")
|
||||
write!(f, "{dest} <- {op} user[{src}]")
|
||||
}
|
||||
op @ OpCode::LoadConst => {
|
||||
let (dest, src) = unsafe { &self.args.load_store };
|
||||
write!(f, "{dest} <- {op} mem[{src}]")
|
||||
}
|
||||
op @ (OpCode::StoreBuiltin | OpCode::LoadBultin) => {
|
||||
let (dest, src) = unsafe { &self.args.load_store };
|
||||
write!(f, "{dest} <- {op} intrinsic[{src}]")
|
||||
}
|
||||
op @ OpCode::BrIf => {
|
||||
let (cond, label) = unsafe { self.args.br_if };
|
||||
write!(f, "{op} {cond}, {label}")
|
||||
@@ -375,9 +394,11 @@ impl Display for OpCode {
|
||||
Self::Divide => "div",
|
||||
Self::Raise => "pow",
|
||||
Self::Modulo => "mod",
|
||||
Self::Load => "vload",
|
||||
Self::LoadUser => "vload",
|
||||
Self::LoadBultin => "iload",
|
||||
Self::LoadConst => "cload",
|
||||
Self::Store => "vstore",
|
||||
Self::StoreUser => "vstore",
|
||||
Self::StoreBuiltin => "istore",
|
||||
Self::Copy => "cpy",
|
||||
Self::IntrinsicCall => "icall",
|
||||
Self::UserCall => "ucall",
|
||||
|
||||
+23
-18
@@ -9,20 +9,21 @@ use std::mem::forget;
|
||||
|
||||
use bumpalo::{Bump, collections::Vec};
|
||||
use indexmap::IndexSet;
|
||||
use parser::{Atom, BinaryOperator, Expr, ExprNode, UnaryOperator};
|
||||
use parser::{Atom, BinaryOperator, Expr, ExprNode, UnaryOperator, Variable};
|
||||
|
||||
use crate::ir::{Hint, HintedReg, Instruction, Label, NonLocal, OpCode, Reg};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
|
||||
pub struct Value(f64); // TODO: use NaN-boxing.
|
||||
use crate::{
|
||||
ir::{Hint, HintedReg, Instruction, Label, NonLocal, OpCode, Reg},
|
||||
vm::{ExecMode, Interpreter, SymbolTable, Value},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Code<'a> {
|
||||
arena: &'a Bump,
|
||||
bc: Bytecode<'a>,
|
||||
consts: &'a mut IndexSet<Value>,
|
||||
free_regs: Vec<'a, Reg>,
|
||||
reg_pointer: u16,
|
||||
pub struct Code<'arena> {
|
||||
pub arena: &'arena Bump,
|
||||
pub bc: Bytecode<'arena>,
|
||||
pub consts: IndexSet<Value>,
|
||||
pub symbols: SymbolTable<'arena>,
|
||||
free_regs: Vec<'arena, Reg>,
|
||||
pub reg_pointer: u16,
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
@@ -38,10 +39,10 @@ impl Code<'_> {
|
||||
fn lower_expr_into(&mut self, expr: &Expr, dest: Reg) -> Hint {
|
||||
match expr {
|
||||
Expr::Leaf(atom) => match atom {
|
||||
Atom::Variable(var) => {
|
||||
let src = NonLocal(crate::index_for_global(var));
|
||||
Atom::Variable(Variable::User(ident)) => {
|
||||
let src = self.symbols.register_user_var(ident, self.arena);
|
||||
self.bc
|
||||
.emit(Instruction::load_store(OpCode::Load, dest, src));
|
||||
.emit(Instruction::load_store(OpCode::LoadUser, dest, src));
|
||||
}
|
||||
&Atom::Number(n) => {
|
||||
let src = self.register_const(Value(n));
|
||||
@@ -106,8 +107,8 @@ impl Code<'_> {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Bytecode<'a> {
|
||||
code: Vec<'a, Instruction>,
|
||||
pub struct Bytecode<'a> {
|
||||
pub code: Vec<'a, Instruction>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -167,13 +168,17 @@ pub fn test_interpreter(expr: &Expr<'_>) -> impl Display {
|
||||
let mut c = Code {
|
||||
arena: &bump,
|
||||
bc: Bytecode::new_in(&bump),
|
||||
consts: &mut IndexSet::new(),
|
||||
consts: IndexSet::new(),
|
||||
symbols: SymbolTable::new_in(&bump),
|
||||
reg_pointer: 0,
|
||||
free_regs: Vec::new_in(&bump),
|
||||
};
|
||||
let result = c.lower_expr(expr);
|
||||
forget(result);
|
||||
c.to_string()
|
||||
let code = c.to_string();
|
||||
let mut vm = Interpreter::new(ExecMode::Uu, c);
|
||||
vm.run();
|
||||
format!("{code}---\n{vm:#?}")
|
||||
}
|
||||
|
||||
impl From<UnaryOperator> for OpCode {
|
||||
|
||||
+3
-25
@@ -3,34 +3,12 @@
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// files that was distributed with this source code.
|
||||
|
||||
mod ir;
|
||||
#![allow(dead_code)]
|
||||
|
||||
use color_eyre::eyre::Result;
|
||||
use either::Either;
|
||||
pub(crate) mod ir;
|
||||
mod vm;
|
||||
|
||||
pub use ir::test_interpreter;
|
||||
use parser::Variable;
|
||||
|
||||
pub enum BuiltinCommand {}
|
||||
pub enum BuiltinVar {}
|
||||
|
||||
pub type Command<'a> = Either<BuiltinCommand, &'a str>;
|
||||
// pub type Variable<'a> = Either<BuiltinVar, &'a str>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Interpreter;
|
||||
|
||||
impl Interpreter {
|
||||
#[tracing::instrument]
|
||||
pub fn run(self) -> Result<Option<i32>> {
|
||||
todo!()
|
||||
}
|
||||
pub fn eval_expression(&mut self) {}
|
||||
}
|
||||
|
||||
fn index_for_global(_var: &Variable) -> u16 {
|
||||
1 // TODO
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[allow(dead_code)]
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
use std::ptr::NonNull;
|
||||
|
||||
// / NaN-boxed value
|
||||
union AValue {
|
||||
float: f64,
|
||||
ptr: NonNull<()>,
|
||||
}
|
||||
|
||||
impl AValue {
|
||||
fn get_float(&self) -> f64 {
|
||||
let float = unsafe { self.float };
|
||||
if !float.is_nan() { float } else { todo!() }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
use bumpalo::{Bump, collections::Vec};
|
||||
use hashbrown::{DefaultHashBuilder, HashMap};
|
||||
use indexmap::{IndexMap, IndexSet};
|
||||
use parser::Identifier;
|
||||
|
||||
use crate::ir::{
|
||||
NonLocal, OpCode, Reg,
|
||||
lower::{Bytecode, Code},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd)]
|
||||
#[repr(transparent)]
|
||||
pub struct Value(pub f64); // TODO: use NaN-boxing.
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ExecMode {
|
||||
Uu,
|
||||
Gnu,
|
||||
Posix,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Interpreter<'a> {
|
||||
arena: &'a Bump,
|
||||
bc: Bytecode<'a>,
|
||||
program_counter: usize,
|
||||
registers: Registers<'a>,
|
||||
symbols: SymbolTable<'a>,
|
||||
consts: IndexSet<Value>,
|
||||
compat: ExecMode,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Registers<'a>(Vec<'a, Value>);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SymbolTable<'a> {
|
||||
user: IndexMap<Identifier<'a>, Value>,
|
||||
// separate table for cheap invalidation. It's an arena _visibly shrugs_.
|
||||
records: HashMap<usize, Value, DefaultHashBuilder, &'a Bump>,
|
||||
// etc
|
||||
}
|
||||
|
||||
impl<'a> Interpreter<'a> {
|
||||
pub fn new(compat: ExecMode, code: Code<'a>) -> Self {
|
||||
Self {
|
||||
arena: code.arena,
|
||||
bc: code.bc,
|
||||
program_counter: 0,
|
||||
registers: Registers(bumpalo::vec![in code.arena; Value(0.); 8]),
|
||||
symbols: code.symbols,
|
||||
consts: code.consts,
|
||||
compat,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SymbolTable<'a> {
|
||||
pub fn new_in(arena: &'a Bump) -> Self {
|
||||
Self {
|
||||
user: IndexMap::new(),
|
||||
records: HashMap::new_in(arena),
|
||||
}
|
||||
}
|
||||
fn lookup_user_var(&self, var: NonLocal) -> &Value {
|
||||
self.user.get_index(var.0 as _).unwrap().1
|
||||
}
|
||||
|
||||
fn write_user_val(&mut self, var: NonLocal, value: &Value) {
|
||||
*self.user.get_index_mut(var.0 as _).unwrap().1 = Value::clone(value);
|
||||
}
|
||||
|
||||
pub fn register_user_var(&mut self, var: &Identifier, bump: &'a Bump) -> NonLocal {
|
||||
if let Some(index) = self.user.get_index_of(var) {
|
||||
NonLocal(index as _)
|
||||
} else {
|
||||
let ident = Identifier {
|
||||
namespace: bump.alloc_str(var.namespace),
|
||||
literal: bump.alloc_str(var.literal),
|
||||
};
|
||||
NonLocal(self.user.insert_full(ident, Value(0.)).0 as _)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Interpreter<'_> {
|
||||
pub fn run(&mut self) {
|
||||
while let Some(instr) = self.bc.code.get(self.program_counter) {
|
||||
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 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),
|
||||
_ => todo!(),
|
||||
};
|
||||
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.get_index(src.0 as _).unwrap()),
|
||||
OpCode::LoadUser => {
|
||||
self.registers
|
||||
.write(dest, self.symbols.lookup_user_var(src));
|
||||
}
|
||||
OpCode::StoreUser => {
|
||||
self.symbols.write_user_val(src, self.registers.read(dest));
|
||||
}
|
||||
_ => todo!(),
|
||||
},
|
||||
_ => todo!(),
|
||||
}
|
||||
self.program_counter += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Registers<'_> {
|
||||
fn read(&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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user