1022 lines
33 KiB
Rust
1022 lines
33 KiB
Rust
use fix_builtins::{BUILTINS, BuiltinId};
|
|
use fix_common::StringId;
|
|
use fix_ir::{Attr, BinOpKind, Ir, MaybeThunk, Param, RawIrRef, ThunkId, UnOpKind};
|
|
use hashbrown::HashMap;
|
|
use num_enum::TryFromPrimitive;
|
|
use rnix::TextRange;
|
|
use string_interner::Symbol as _;
|
|
|
|
pub mod disassembler;
|
|
|
|
pub struct InstructionPtr(pub usize);
|
|
|
|
pub trait BytecodeContext {
|
|
fn intern_string(&mut self, s: &str) -> StringId;
|
|
fn register_span(&mut self, range: TextRange) -> u32;
|
|
fn get_code(&self) -> &[u8];
|
|
fn get_code_mut(&mut self) -> &mut Vec<u8>;
|
|
fn add_constant(&mut self, val: Const) -> u32;
|
|
}
|
|
|
|
#[repr(u8)]
|
|
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
|
|
#[allow(clippy::enum_variant_names)]
|
|
pub enum Op {
|
|
PushSmi,
|
|
PushBigInt,
|
|
PushFloat,
|
|
PushString,
|
|
PushNull,
|
|
PushTrue,
|
|
PushFalse,
|
|
|
|
LoadLocal,
|
|
LoadOuter,
|
|
StoreLocal,
|
|
AllocLocals,
|
|
|
|
MakeThunk,
|
|
MakeClosure,
|
|
MakePatternClosure,
|
|
|
|
Call,
|
|
DispatchPrimOp,
|
|
|
|
MakeAttrs,
|
|
MakeEmptyAttrs,
|
|
SelectStatic,
|
|
SelectDynamic,
|
|
HasAttrPathStatic,
|
|
HasAttrPathDynamic,
|
|
HasAttrStatic,
|
|
HasAttrDynamic,
|
|
HasAttrResolve,
|
|
JumpIfSelectSucceeded,
|
|
JumpIfSelectFailed,
|
|
|
|
MakeList,
|
|
MakeEmptyList,
|
|
|
|
OpAdd,
|
|
OpSub,
|
|
OpMul,
|
|
OpDiv,
|
|
OpEq,
|
|
OpNeq,
|
|
OpLt,
|
|
OpGt,
|
|
OpLeq,
|
|
OpGeq,
|
|
OpConcat,
|
|
OpUpdate,
|
|
|
|
OpNeg,
|
|
OpNot,
|
|
|
|
JumpIfFalse,
|
|
JumpIfTrue,
|
|
Jump,
|
|
|
|
ConcatStrings,
|
|
ResolvePath,
|
|
|
|
Assert,
|
|
|
|
PushWith,
|
|
PopWith,
|
|
LookupWith,
|
|
PrepareWith,
|
|
|
|
LoadBuiltins,
|
|
LoadBuiltin,
|
|
|
|
MkPos,
|
|
|
|
LoadReplBinding,
|
|
LoadScopedBinding,
|
|
|
|
Return,
|
|
|
|
Illegal,
|
|
}
|
|
|
|
struct ScopeInfo {
|
|
depth: u8,
|
|
thunk_map: HashMap<ThunkId, u32>,
|
|
}
|
|
|
|
struct BytecodeEmitter<'a, Ctx: BytecodeContext> {
|
|
ctx: &'a mut Ctx,
|
|
scope_stack: Vec<ScopeInfo>,
|
|
}
|
|
|
|
#[repr(u8)]
|
|
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
|
|
pub enum OperandType {
|
|
Const,
|
|
BigInt,
|
|
Local,
|
|
BuiltinConst,
|
|
Builtins,
|
|
ReplBinding,
|
|
ScopedImportBinding,
|
|
WithLookup,
|
|
}
|
|
|
|
pub enum Const {
|
|
Smi(i32),
|
|
Float(f64),
|
|
Bool(bool),
|
|
String(StringId),
|
|
Path(StringId),
|
|
PrimOp {
|
|
id: BuiltinId,
|
|
arity: u8,
|
|
dispatch_ip: u32,
|
|
},
|
|
Null,
|
|
}
|
|
|
|
#[repr(u8)]
|
|
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
|
|
pub enum AttrKeyType {
|
|
Static,
|
|
Dynamic,
|
|
}
|
|
|
|
pub enum InlineOperand {
|
|
Const(Const),
|
|
BigInt(i64),
|
|
Local { layer: u8, local: u32 },
|
|
BuiltinConst(StringId),
|
|
Builtins,
|
|
ReplBinding(StringId),
|
|
ScopedImportBinding(StringId),
|
|
WithLookup(StringId),
|
|
}
|
|
|
|
pub fn compile_bytecode(ir: RawIrRef<'_>, ctx: &mut impl BytecodeContext) -> InstructionPtr {
|
|
let ip = ctx.get_code().len();
|
|
let mut emitter = BytecodeEmitter::new(ctx);
|
|
emitter.emit_toplevel(ir);
|
|
InstructionPtr(ip)
|
|
}
|
|
|
|
impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|
fn new(ctx: &'a mut Ctx) -> Self {
|
|
Self {
|
|
ctx,
|
|
scope_stack: Vec::with_capacity(32),
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
fn inline_maybe_thunk(&self, val: &MaybeThunk) -> InlineOperand {
|
|
use MaybeThunk::*;
|
|
match *val {
|
|
Int(x) => {
|
|
if let Ok(x) = x.try_into() {
|
|
InlineOperand::Const(Const::Smi(x))
|
|
} else {
|
|
InlineOperand::BigInt(x)
|
|
}
|
|
}
|
|
Float(x) => InlineOperand::Const(Const::Float(x)),
|
|
Bool(b) => InlineOperand::Const(Const::Bool(b)),
|
|
Null => InlineOperand::Const(Const::Null),
|
|
Str(id) => InlineOperand::Const(Const::String(id)),
|
|
Path(id) => InlineOperand::Const(Const::String(id)),
|
|
Thunk(id) => {
|
|
let (layer, local) = self.resolve_thunk(id);
|
|
InlineOperand::Local { layer, local }
|
|
}
|
|
Arg { layer } => InlineOperand::Local { layer, local: 0 },
|
|
Builtin(id) => {
|
|
let (_, arity) = BUILTINS[id as usize];
|
|
InlineOperand::Const(Const::PrimOp {
|
|
id,
|
|
arity,
|
|
dispatch_ip: id.entry_phase().ip(),
|
|
})
|
|
}
|
|
BuiltinConst(id) => InlineOperand::BuiltinConst(id),
|
|
Builtins => InlineOperand::Builtins,
|
|
ReplBinding(id) => InlineOperand::ReplBinding(id),
|
|
ScopedImportBinding(id) => InlineOperand::ScopedImportBinding(id),
|
|
WithLookup(id) => InlineOperand::WithLookup(id),
|
|
}
|
|
}
|
|
|
|
fn emit_maybe_thunk(&mut self, val: &MaybeThunk) {
|
|
use InlineOperand::*;
|
|
let operand = self.inline_maybe_thunk(val);
|
|
match operand {
|
|
Const(val) => {
|
|
let idx = self.ctx.add_constant(val);
|
|
self.emit_u8(OperandType::Const as u8);
|
|
self.emit_u32(idx);
|
|
}
|
|
BigInt(val) => {
|
|
self.emit_u8(OperandType::BigInt as u8);
|
|
self.emit_i64(val);
|
|
}
|
|
Local { layer, local } => {
|
|
self.emit_u8(OperandType::Local as u8);
|
|
self.emit_u8(layer);
|
|
self.emit_u32(local);
|
|
}
|
|
BuiltinConst(id) => {
|
|
self.emit_u8(OperandType::BuiltinConst as u8);
|
|
self.emit_str_id(id);
|
|
}
|
|
Builtins => {
|
|
self.emit_u8(OperandType::Builtins as u8);
|
|
}
|
|
ReplBinding(id) => {
|
|
self.emit_u8(OperandType::ReplBinding as u8);
|
|
self.emit_str_id(id);
|
|
}
|
|
ScopedImportBinding(id) => {
|
|
self.emit_u8(OperandType::ScopedImportBinding as u8);
|
|
self.emit_str_id(id);
|
|
}
|
|
WithLookup(id) => {
|
|
self.emit_u8(OperandType::WithLookup as u8);
|
|
self.emit_str_id(id);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn emit_op(&mut self, op: Op) {
|
|
self.ctx.get_code_mut().push(op as u8);
|
|
}
|
|
|
|
#[inline]
|
|
fn emit_u8(&mut self, val: u8) {
|
|
self.ctx.get_code_mut().push(val);
|
|
}
|
|
|
|
#[inline]
|
|
fn emit_u16(&mut self, val: u16) {
|
|
self.ctx
|
|
.get_code_mut()
|
|
.extend_from_slice(&val.to_le_bytes());
|
|
}
|
|
|
|
#[inline]
|
|
fn emit_u32(&mut self, val: u32) {
|
|
self.ctx
|
|
.get_code_mut()
|
|
.extend_from_slice(&val.to_le_bytes());
|
|
}
|
|
|
|
#[inline]
|
|
fn emit_i32(&mut self, val: i32) {
|
|
self.ctx
|
|
.get_code_mut()
|
|
.extend_from_slice(&val.to_le_bytes());
|
|
}
|
|
|
|
#[inline]
|
|
fn emit_i64(&mut self, val: i64) {
|
|
self.ctx
|
|
.get_code_mut()
|
|
.extend_from_slice(&val.to_le_bytes());
|
|
}
|
|
|
|
#[inline]
|
|
fn emit_f64(&mut self, val: f64) {
|
|
self.ctx
|
|
.get_code_mut()
|
|
.extend_from_slice(&val.to_le_bytes());
|
|
}
|
|
|
|
#[inline]
|
|
fn emit_i32_placeholder(&mut self) -> usize {
|
|
let offset = self.ctx.get_code_mut().len();
|
|
self.ctx.get_code_mut().extend_from_slice(&[0u8; 4]);
|
|
offset
|
|
}
|
|
#[inline]
|
|
fn patch_i32(&mut self, offset: usize, val: i32) {
|
|
self.ctx.get_code_mut()[offset..offset + 4].copy_from_slice(&val.to_le_bytes());
|
|
}
|
|
|
|
#[inline]
|
|
fn emit_jump_placeholder(&mut self) -> usize {
|
|
self.emit_op(Op::Jump);
|
|
self.emit_i32_placeholder()
|
|
}
|
|
|
|
#[inline]
|
|
fn patch_jump_target(&mut self, placeholder_offset: usize) {
|
|
let current_pos = self.ctx.get_code_mut().len();
|
|
let relative_offset = (current_pos as i32) - (placeholder_offset as i32) - 4;
|
|
self.patch_i32(placeholder_offset, relative_offset);
|
|
}
|
|
|
|
#[inline]
|
|
fn emit_str_id(&mut self, id: StringId) {
|
|
self.ctx
|
|
.get_code_mut()
|
|
.extend_from_slice(&(id.0.to_usize() as u32).to_le_bytes());
|
|
}
|
|
|
|
fn current_depth(&self) -> u8 {
|
|
self.scope_stack.last().map_or(0, |s| s.depth)
|
|
}
|
|
|
|
fn resolve_thunk(&self, id: ThunkId) -> (u8, u32) {
|
|
for scope in self.scope_stack.iter().rev() {
|
|
if let Some(&local_idx) = scope.thunk_map.get(&id) {
|
|
let layer = self.current_depth() - scope.depth;
|
|
return (layer, local_idx);
|
|
}
|
|
}
|
|
panic!("ThunkId {:?} not found in any scope", id);
|
|
}
|
|
|
|
fn emit_load(&mut self, layer: u8, local: u32) {
|
|
if layer == 0 {
|
|
self.emit_op(Op::LoadLocal);
|
|
self.emit_u32(local);
|
|
} else {
|
|
self.emit_op(Op::LoadOuter);
|
|
self.emit_u8(layer);
|
|
self.emit_u32(local);
|
|
}
|
|
}
|
|
|
|
fn count_with_thunks(&self, ir: RawIrRef<'_>) -> usize {
|
|
match ir {
|
|
Ir::With { thunks, body, .. } => thunks.len() + self.count_with_thunks(*body),
|
|
Ir::TopLevel { thunks, body } => thunks.len() + self.count_with_thunks(*body),
|
|
Ir::If { cond, consq, alter } => {
|
|
self.count_with_thunks(*cond)
|
|
+ self.count_with_thunks(*consq)
|
|
+ self.count_with_thunks(*alter)
|
|
}
|
|
Ir::BinOp { lhs, rhs, .. } => {
|
|
self.count_with_thunks(*lhs) + self.count_with_thunks(*rhs)
|
|
}
|
|
Ir::UnOp { rhs, .. } => self.count_with_thunks(*rhs),
|
|
Ir::Call { func, .. } => self.count_with_thunks(*func),
|
|
Ir::Assert {
|
|
assertion, expr, ..
|
|
} => self.count_with_thunks(*assertion) + self.count_with_thunks(*expr),
|
|
Ir::Select { expr, .. } => self.count_with_thunks(*expr),
|
|
Ir::HasAttr { lhs, .. } => self.count_with_thunks(*lhs),
|
|
Ir::ConcatStrings { parts, .. } => {
|
|
parts.iter().map(|p| self.count_with_thunks(*p)).sum()
|
|
}
|
|
_ => 0,
|
|
}
|
|
}
|
|
|
|
fn collect_all_thunks<'ir>(
|
|
&self,
|
|
own_thunks: &[(ThunkId, RawIrRef<'ir>)],
|
|
body: RawIrRef<'ir>,
|
|
) -> Vec<(ThunkId, RawIrRef<'ir>)> {
|
|
let mut all = Vec::from(own_thunks);
|
|
self.collect_with_thunks_recursive(body, &mut all);
|
|
let mut i = 0;
|
|
while i < all.len() {
|
|
let thunk_body = all[i].1;
|
|
self.collect_with_thunks_recursive(thunk_body, &mut all);
|
|
i += 1;
|
|
}
|
|
all
|
|
}
|
|
|
|
fn collect_with_thunks_recursive<'ir>(
|
|
&self,
|
|
ir: RawIrRef<'ir>,
|
|
out: &mut Vec<(ThunkId, RawIrRef<'ir>)>,
|
|
) {
|
|
match ir {
|
|
Ir::With { thunks, body, .. } => {
|
|
for &(id, inner) in thunks.iter() {
|
|
out.push((id, inner));
|
|
}
|
|
self.collect_with_thunks_recursive(*body, out);
|
|
}
|
|
Ir::TopLevel { thunks, body } => {
|
|
for &(id, inner) in thunks.iter() {
|
|
out.push((id, inner));
|
|
}
|
|
self.collect_with_thunks_recursive(*body, out);
|
|
}
|
|
Ir::If { cond, consq, alter } => {
|
|
self.collect_with_thunks_recursive(*cond, out);
|
|
self.collect_with_thunks_recursive(*consq, out);
|
|
self.collect_with_thunks_recursive(*alter, out);
|
|
}
|
|
Ir::BinOp { lhs, rhs, .. } => {
|
|
self.collect_with_thunks_recursive(*lhs, out);
|
|
self.collect_with_thunks_recursive(*rhs, out);
|
|
}
|
|
Ir::UnOp { rhs, .. } => self.collect_with_thunks_recursive(*rhs, out),
|
|
Ir::Call { func, .. } => {
|
|
self.collect_with_thunks_recursive(*func, out);
|
|
}
|
|
Ir::Assert {
|
|
assertion, expr, ..
|
|
} => {
|
|
self.collect_with_thunks_recursive(*assertion, out);
|
|
self.collect_with_thunks_recursive(*expr, out);
|
|
}
|
|
Ir::Select { expr, .. } => {
|
|
self.collect_with_thunks_recursive(*expr, out);
|
|
}
|
|
Ir::HasAttr { lhs, .. } => self.collect_with_thunks_recursive(*lhs, out),
|
|
Ir::ConcatStrings { parts, .. } => {
|
|
for p in parts.iter() {
|
|
self.collect_with_thunks_recursive(*p, out);
|
|
}
|
|
}
|
|
_ => (),
|
|
}
|
|
}
|
|
|
|
fn push_scope(&mut self, has_arg: bool, thunk_ids: &[ThunkId]) {
|
|
let depth = self.scope_stack.len().try_into().expect("scope too deep!");
|
|
let thunk_base = if has_arg { 1u32 } else { 0u32 };
|
|
let thunk_map = thunk_ids
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(i, &id)| (id, thunk_base + i as u32))
|
|
.collect();
|
|
self.scope_stack.push(ScopeInfo { depth, thunk_map });
|
|
}
|
|
|
|
fn pop_scope(&mut self) {
|
|
self.scope_stack.pop();
|
|
}
|
|
|
|
fn emit_toplevel(&mut self, ir: RawIrRef<'_>) {
|
|
match ir {
|
|
&Ir::TopLevel { body, ref thunks } => {
|
|
let with_thunk_count = self.count_with_thunks(body);
|
|
let total_slots = thunks.len() + with_thunk_count;
|
|
|
|
let all_thunks = self.collect_all_thunks(thunks, body);
|
|
let thunk_ids: Vec<ThunkId> = all_thunks.iter().map(|&(id, _)| id).collect();
|
|
|
|
self.push_scope(false, &thunk_ids);
|
|
|
|
if total_slots > 0 {
|
|
self.emit_op(Op::AllocLocals);
|
|
self.emit_u32(total_slots as u32);
|
|
}
|
|
|
|
self.emit_scope_thunks(thunks);
|
|
self.emit_expr(body);
|
|
self.emit_op(Op::Return);
|
|
|
|
self.pop_scope();
|
|
}
|
|
_ => {
|
|
self.push_scope(false, &[]);
|
|
self.emit_expr(ir);
|
|
self.emit_op(Op::Return);
|
|
self.pop_scope();
|
|
}
|
|
}
|
|
}
|
|
|
|
fn emit_scope_thunks(&mut self, thunks: &[(ThunkId, RawIrRef<'_>)]) {
|
|
for &(id, inner) in thunks {
|
|
let skip_patch = self.emit_jump_placeholder();
|
|
let entry_point = self.ctx.get_code_mut().len() as u32;
|
|
self.emit_expr(inner);
|
|
self.emit_op(Op::Return);
|
|
self.patch_jump_target(skip_patch);
|
|
self.emit_op(Op::MakeThunk);
|
|
self.emit_u32(entry_point);
|
|
let (_, local_idx) = self.resolve_thunk(id);
|
|
self.emit_op(Op::StoreLocal);
|
|
self.emit_u32(local_idx);
|
|
}
|
|
}
|
|
|
|
fn emit_expr(&mut self, ir: RawIrRef<'_>) {
|
|
match ir {
|
|
&Ir::Int(x) => {
|
|
if let Ok(x) = x.try_into() {
|
|
self.emit_op(Op::PushSmi);
|
|
self.emit_i32(x);
|
|
} else {
|
|
self.emit_op(Op::PushBigInt);
|
|
self.emit_i64(x);
|
|
}
|
|
}
|
|
&Ir::Float(x) => {
|
|
self.emit_op(Op::PushFloat);
|
|
self.emit_f64(x);
|
|
}
|
|
&Ir::Bool(true) => self.emit_op(Op::PushTrue),
|
|
&Ir::Bool(false) => self.emit_op(Op::PushFalse),
|
|
Ir::Null => self.emit_op(Op::PushNull),
|
|
&Ir::Str(id) => {
|
|
self.emit_op(Op::PushString);
|
|
self.emit_str_id(id);
|
|
}
|
|
&Ir::Path(p) => {
|
|
self.emit_expr(p);
|
|
self.emit_op(Op::ResolvePath);
|
|
}
|
|
&Ir::If { cond, consq, alter } => {
|
|
self.emit_expr(cond);
|
|
|
|
self.emit_op(Op::JumpIfFalse);
|
|
let else_placeholder = self.emit_i32_placeholder();
|
|
let after_jif = self.ctx.get_code_mut().len();
|
|
|
|
self.emit_expr(consq);
|
|
|
|
self.emit_op(Op::Jump);
|
|
let end_placeholder = self.emit_i32_placeholder();
|
|
let after_jump = self.ctx.get_code_mut().len();
|
|
|
|
let else_offset = (after_jump as i32) - (after_jif as i32);
|
|
self.patch_i32(else_placeholder, else_offset);
|
|
|
|
self.emit_expr(alter);
|
|
|
|
let end_offset = (self.ctx.get_code_mut().len() as i32) - (after_jump as i32);
|
|
self.patch_i32(end_placeholder, end_offset);
|
|
}
|
|
&Ir::BinOp { lhs, rhs, kind } => {
|
|
self.emit_binop(lhs, rhs, kind);
|
|
}
|
|
&Ir::UnOp { rhs, kind } => match kind {
|
|
UnOpKind::Neg => {
|
|
self.emit_expr(rhs);
|
|
self.emit_op(Op::OpNeg);
|
|
}
|
|
UnOpKind::Not => {
|
|
self.emit_expr(rhs);
|
|
self.emit_op(Op::OpNot);
|
|
}
|
|
},
|
|
&Ir::Func {
|
|
body,
|
|
ref param,
|
|
ref thunks,
|
|
} => {
|
|
self.emit_func(thunks, param, body);
|
|
}
|
|
Ir::AttrSet { stcs, dyns } => {
|
|
self.emit_attrset(stcs, dyns);
|
|
}
|
|
Ir::List { items } => {
|
|
if items.is_empty() {
|
|
self.emit_op(Op::MakeEmptyList);
|
|
} else {
|
|
self.emit_op(Op::MakeList);
|
|
self.emit_u32(items.len() as u32);
|
|
for &item in items.iter() {
|
|
self.emit_maybe_thunk(item);
|
|
}
|
|
}
|
|
}
|
|
&Ir::Call { func, arg, .. } => {
|
|
self.emit_expr(func);
|
|
self.emit_op(Op::Call);
|
|
self.emit_maybe_thunk(arg);
|
|
}
|
|
&Ir::Arg { layer } => {
|
|
self.emit_load(layer, 0);
|
|
}
|
|
&Ir::TopLevel { body, ref thunks } => {
|
|
self.emit_toplevel_inner(body, thunks);
|
|
}
|
|
&Ir::Select {
|
|
expr,
|
|
ref attrpath,
|
|
default,
|
|
span,
|
|
} => {
|
|
self.emit_select(expr, attrpath, default, span);
|
|
}
|
|
Ir::Builtins => {
|
|
self.emit_op(Op::LoadBuiltins);
|
|
}
|
|
&Ir::Builtin(id) => {
|
|
self.emit_op(Op::LoadBuiltin);
|
|
self.emit_u8(id as u8);
|
|
}
|
|
&Ir::BuiltinConst(id) => {
|
|
self.emit_select(
|
|
&Ir::Builtins,
|
|
&[Attr::Str(id, TextRange::default())],
|
|
None,
|
|
TextRange::default(),
|
|
);
|
|
}
|
|
&Ir::ConcatStrings {
|
|
parts: _,
|
|
force_string: _,
|
|
} => {
|
|
todo!("redesign ConcatStrings");
|
|
// self.emit_op(Op::ConcatStrings);
|
|
// self.emit_u16(parts.len() as u16);
|
|
// self.emit_u8(if force_string { 1 } else { 0 });
|
|
// for &part in parts.iter() {
|
|
// let operand = self.inline_maybe_thunk(part);
|
|
// self.emit_inline_operand(operand);
|
|
// }
|
|
}
|
|
&Ir::HasAttr { lhs, ref rhs } => {
|
|
self.emit_has_attr(lhs, rhs);
|
|
}
|
|
Ir::Assert {
|
|
assertion,
|
|
expr,
|
|
assertion_raw,
|
|
span,
|
|
} => {
|
|
let raw_idx = self.ctx.intern_string(assertion_raw);
|
|
let span_id = self.ctx.register_span(*span);
|
|
self.emit_expr(*assertion);
|
|
self.emit_expr(*expr);
|
|
self.emit_op(Op::Assert);
|
|
self.emit_str_id(raw_idx);
|
|
self.emit_u32(span_id);
|
|
}
|
|
&Ir::CurPos(span) => {
|
|
let span_id = self.ctx.register_span(span);
|
|
self.emit_op(Op::MkPos);
|
|
self.emit_u32(span_id);
|
|
}
|
|
&Ir::ReplBinding(name) => {
|
|
self.emit_op(Op::LoadReplBinding);
|
|
self.emit_str_id(name);
|
|
}
|
|
&Ir::ScopedImportBinding(name) => {
|
|
self.emit_op(Op::LoadScopedBinding);
|
|
self.emit_str_id(name);
|
|
}
|
|
&Ir::With {
|
|
namespace,
|
|
body,
|
|
ref thunks,
|
|
} => {
|
|
self.emit_with(namespace, body, thunks);
|
|
}
|
|
&Ir::WithLookup(name) => {
|
|
// TODO: specialize shallow with lookups
|
|
self.emit_op(Op::PrepareWith);
|
|
self.emit_op(Op::LookupWith);
|
|
self.emit_str_id(name);
|
|
}
|
|
&Ir::MaybeThunk(thunk) => {
|
|
use MaybeThunk::*;
|
|
match *thunk {
|
|
Int(x) => {
|
|
if let Ok(x) = x.try_into() {
|
|
self.emit_op(Op::PushSmi);
|
|
self.emit_i32(x);
|
|
} else {
|
|
self.emit_op(Op::PushBigInt);
|
|
self.emit_i64(x);
|
|
}
|
|
}
|
|
Float(x) => {
|
|
self.emit_op(Op::PushFloat);
|
|
self.emit_f64(x);
|
|
}
|
|
Bool(true) => self.emit_op(Op::PushTrue),
|
|
Bool(false) => self.emit_op(Op::PushFalse),
|
|
Null => self.emit_op(Op::PushNull),
|
|
Str(id) => {
|
|
self.emit_op(Op::PushString);
|
|
self.emit_str_id(id);
|
|
}
|
|
Path(id) => {
|
|
self.emit_op(Op::PushString);
|
|
self.emit_str_id(id);
|
|
self.emit_op(Op::ResolvePath);
|
|
}
|
|
Thunk(id) => {
|
|
let (layer, local) = self.resolve_thunk(id);
|
|
self.emit_load(layer, local);
|
|
}
|
|
Arg { layer } => self.emit_load(layer, 0),
|
|
Builtin(id) => {
|
|
self.emit_op(Op::LoadBuiltin);
|
|
self.emit_u8(id as u8);
|
|
}
|
|
BuiltinConst(id) => self.emit_select(
|
|
&Ir::Builtins,
|
|
&[Attr::Str(id, TextRange::default())],
|
|
None,
|
|
TextRange::default(),
|
|
),
|
|
Builtins => self.emit_op(Op::LoadBuiltins),
|
|
ReplBinding(name) => {
|
|
self.emit_op(Op::LoadReplBinding);
|
|
self.emit_str_id(name);
|
|
}
|
|
ScopedImportBinding(name) => {
|
|
self.emit_op(Op::LoadScopedBinding);
|
|
self.emit_str_id(name);
|
|
}
|
|
WithLookup(name) => {
|
|
// TODO: specialize shallow with lookups
|
|
self.emit_op(Op::PrepareWith);
|
|
self.emit_op(Op::LookupWith);
|
|
self.emit_str_id(name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn emit_binop(&mut self, lhs: RawIrRef<'_>, rhs: RawIrRef<'_>, kind: BinOpKind) {
|
|
use BinOpKind::*;
|
|
match kind {
|
|
And => {
|
|
self.emit_expr(lhs);
|
|
self.emit_op(Op::JumpIfFalse);
|
|
let skip_placeholder = self.emit_i32_placeholder();
|
|
let after_jif = self.ctx.get_code_mut().len();
|
|
|
|
self.emit_expr(rhs);
|
|
self.emit_op(Op::Jump);
|
|
let end_placeholder = self.emit_i32_placeholder();
|
|
let after_jump = self.ctx.get_code_mut().len();
|
|
|
|
let false_offset = (after_jump as i32) - (after_jif as i32);
|
|
self.patch_i32(skip_placeholder, false_offset);
|
|
|
|
self.emit_op(Op::PushFalse);
|
|
|
|
let end_offset = (self.ctx.get_code_mut().len() as i32) - (after_jump as i32);
|
|
self.patch_i32(end_placeholder, end_offset);
|
|
}
|
|
Or => {
|
|
self.emit_expr(lhs);
|
|
self.emit_op(Op::JumpIfTrue);
|
|
let skip_placeholder = self.emit_i32_placeholder();
|
|
let after_jit = self.ctx.get_code_mut().len();
|
|
|
|
self.emit_expr(rhs);
|
|
self.emit_op(Op::Jump);
|
|
let end_placeholder = self.emit_i32_placeholder();
|
|
let after_jump = self.ctx.get_code_mut().len();
|
|
|
|
let true_offset = (after_jump as i32) - (after_jit as i32);
|
|
self.patch_i32(skip_placeholder, true_offset);
|
|
|
|
self.emit_op(Op::PushTrue);
|
|
|
|
let end_offset = (self.ctx.get_code_mut().len() as i32) - (after_jump as i32);
|
|
self.patch_i32(end_placeholder, end_offset);
|
|
}
|
|
Impl => {
|
|
self.emit_expr(lhs);
|
|
self.emit_op(Op::JumpIfFalse);
|
|
let skip_placeholder = self.emit_i32_placeholder();
|
|
let after_jif = self.ctx.get_code_mut().len();
|
|
|
|
self.emit_expr(rhs);
|
|
self.emit_op(Op::Jump);
|
|
let end_placeholder = self.emit_i32_placeholder();
|
|
let after_jump = self.ctx.get_code_mut().len();
|
|
|
|
let true_offset = (after_jump as i32) - (after_jif as i32);
|
|
self.patch_i32(skip_placeholder, true_offset);
|
|
|
|
self.emit_op(Op::PushTrue);
|
|
|
|
let end_offset = (self.ctx.get_code_mut().len() as i32) - (after_jump as i32);
|
|
self.patch_i32(end_placeholder, end_offset);
|
|
}
|
|
PipeL => {
|
|
todo!("new call");
|
|
// self.emit_expr(rhs);
|
|
// self.emit_expr(lhs);
|
|
// self.emit_op(Op::Call);
|
|
}
|
|
PipeR => {
|
|
todo!("new call");
|
|
// self.emit_expr(lhs);
|
|
// self.emit_expr(rhs);
|
|
// self.emit_op(Op::Call);
|
|
}
|
|
_ => {
|
|
self.emit_expr(lhs);
|
|
self.emit_expr(rhs);
|
|
self.emit_op(match kind {
|
|
Add => Op::OpAdd,
|
|
Sub => Op::OpSub,
|
|
Mul => Op::OpMul,
|
|
Div => Op::OpDiv,
|
|
Eq => Op::OpEq,
|
|
Neq => Op::OpNeq,
|
|
Lt => Op::OpLt,
|
|
Gt => Op::OpGt,
|
|
Leq => Op::OpLeq,
|
|
Geq => Op::OpGeq,
|
|
Con => Op::OpConcat,
|
|
Upd => Op::OpUpdate,
|
|
_ => unreachable!(),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
fn emit_func<'ir>(
|
|
&mut self,
|
|
thunks: &[(ThunkId, RawIrRef<'ir>)],
|
|
param: &Option<Param<'ir>>,
|
|
body: RawIrRef<'ir>,
|
|
) {
|
|
let with_thunk_count = self.count_with_thunks(body);
|
|
let total_slots = thunks.len() + with_thunk_count;
|
|
|
|
let all_thunks = self.collect_all_thunks(thunks, body);
|
|
let thunk_ids: Vec<ThunkId> = all_thunks.iter().map(|&(id, _)| id).collect();
|
|
|
|
let skip_patch = self.emit_jump_placeholder();
|
|
let entry_point = self.ctx.get_code().len() as u32;
|
|
self.push_scope(true, &thunk_ids);
|
|
self.emit_scope_thunks(thunks);
|
|
self.emit_expr(body);
|
|
self.emit_op(Op::Return);
|
|
self.pop_scope();
|
|
self.patch_jump_target(skip_patch);
|
|
|
|
if let Some(Param {
|
|
required,
|
|
optional,
|
|
ellipsis,
|
|
}) = param
|
|
{
|
|
self.emit_op(Op::MakePatternClosure);
|
|
self.emit_u32(entry_point);
|
|
self.emit_u32(total_slots as u32);
|
|
self.emit_u16(required.len() as u16);
|
|
self.emit_u16(optional.len() as u16);
|
|
self.emit_u8(if *ellipsis { 1 } else { 0 });
|
|
|
|
for &(sym, _) in required.iter() {
|
|
self.emit_str_id(sym);
|
|
}
|
|
for &(sym, _) in optional.iter() {
|
|
self.emit_str_id(sym);
|
|
}
|
|
for &(sym, span) in required.iter().chain(optional.iter()) {
|
|
let span_id = self.ctx.register_span(span);
|
|
self.emit_str_id(sym);
|
|
self.emit_u32(span_id);
|
|
}
|
|
} else {
|
|
self.emit_op(Op::MakeClosure);
|
|
self.emit_u32(entry_point);
|
|
self.emit_u32(total_slots as u32);
|
|
}
|
|
}
|
|
|
|
fn emit_attrset(
|
|
&mut self,
|
|
stcs: &fix_ir::HashMap<'_, StringId, (&MaybeThunk, TextRange)>,
|
|
dyns: &[(RawIrRef<'_>, &MaybeThunk, TextRange)],
|
|
) {
|
|
if stcs.is_empty() && dyns.is_empty() {
|
|
self.emit_op(Op::MakeEmptyAttrs);
|
|
return;
|
|
}
|
|
|
|
let total = stcs.len() + dyns.len();
|
|
self.emit_op(Op::MakeAttrs);
|
|
self.emit_u32(total as u32);
|
|
|
|
for (&sym, &(val, span)) in stcs.iter() {
|
|
self.emit_u8(AttrKeyType::Static as u8);
|
|
self.emit_str_id(sym);
|
|
self.emit_maybe_thunk(val);
|
|
let span_id = self.ctx.register_span(span);
|
|
self.emit_u32(span_id);
|
|
}
|
|
for &(_key, val, span) in dyns.iter() {
|
|
self.emit_u8(AttrKeyType::Dynamic as u8);
|
|
self.emit_maybe_thunk(val);
|
|
let span_id = self.ctx.register_span(span);
|
|
self.emit_u32(span_id);
|
|
}
|
|
}
|
|
|
|
fn emit_select(
|
|
&mut self,
|
|
expr: RawIrRef<'_>,
|
|
attrpath: &[Attr<RawIrRef<'_>>],
|
|
default: Option<RawIrRef<'_>>,
|
|
span: TextRange,
|
|
) {
|
|
self.emit_expr(expr);
|
|
|
|
let mut dynamic_patches = Vec::new();
|
|
for attr in attrpath.iter() {
|
|
match *attr {
|
|
Attr::Str(sym, _) => {
|
|
let span_id = self.ctx.register_span(span);
|
|
self.emit_op(Op::SelectStatic);
|
|
self.emit_u32(span_id);
|
|
self.emit_str_id(sym);
|
|
}
|
|
Attr::Dynamic(key_expr, _) => {
|
|
self.emit_op(Op::JumpIfSelectFailed);
|
|
dynamic_patches.push(self.emit_i32_placeholder());
|
|
self.emit_expr(key_expr);
|
|
let span_id = self.ctx.register_span(span);
|
|
self.emit_op(Op::SelectDynamic);
|
|
self.emit_u32(span_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(default) = default {
|
|
let before: i32 = self.ctx.get_code().len().try_into().unwrap();
|
|
for patch in dynamic_patches {
|
|
self.patch_jump_target(patch);
|
|
}
|
|
self.emit_op(Op::JumpIfSelectSucceeded);
|
|
let placeholder = self.emit_i32_placeholder();
|
|
self.emit_expr(default);
|
|
let after: i32 = self.ctx.get_code().len().try_into().unwrap();
|
|
// Offset is relative to after the placeholder, so subtract the
|
|
// size of JumpIfSelectSucceeded (1) + placeholder (4).
|
|
self.patch_i32(placeholder, after - before - 5);
|
|
} else {
|
|
for patch in dynamic_patches {
|
|
self.patch_jump_target(patch);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn emit_has_attr(&mut self, lhs: RawIrRef<'_>, rhs: &[Attr<RawIrRef<'_>>]) {
|
|
self.emit_expr(lhs);
|
|
|
|
let mut dynamic_patches = Vec::new();
|
|
let [attrs @ .., last] = rhs else {
|
|
panic!("attrpath is empty");
|
|
};
|
|
for attr in attrs {
|
|
match *attr {
|
|
Attr::Str(sym, span) => {
|
|
let span_id = self.ctx.register_span(span);
|
|
self.emit_op(Op::HasAttrPathStatic);
|
|
self.emit_u32(span_id);
|
|
self.emit_str_id(sym);
|
|
}
|
|
Attr::Dynamic(key_expr, span) => {
|
|
self.emit_op(Op::JumpIfSelectFailed);
|
|
dynamic_patches.push(self.emit_i32_placeholder());
|
|
self.emit_expr(key_expr);
|
|
let span_id = self.ctx.register_span(span);
|
|
self.emit_op(Op::HasAttrPathDynamic);
|
|
self.emit_u32(span_id);
|
|
}
|
|
}
|
|
}
|
|
match *last {
|
|
Attr::Str(sym, _) => {
|
|
self.emit_op(Op::HasAttrStatic);
|
|
self.emit_str_id(sym);
|
|
}
|
|
Attr::Dynamic(key_expr, _) => {
|
|
self.emit_op(Op::JumpIfSelectFailed);
|
|
dynamic_patches.push(self.emit_i32_placeholder());
|
|
self.emit_expr(key_expr);
|
|
self.emit_op(Op::HasAttrDynamic);
|
|
}
|
|
}
|
|
for patch in dynamic_patches {
|
|
self.patch_jump_target(patch);
|
|
}
|
|
self.emit_op(Op::HasAttrResolve);
|
|
}
|
|
|
|
fn emit_with(
|
|
&mut self,
|
|
namespace: &MaybeThunk,
|
|
body: RawIrRef<'_>,
|
|
thunks: &[(ThunkId, RawIrRef<'_>)],
|
|
) {
|
|
self.emit_op(Op::PushWith);
|
|
self.emit_maybe_thunk(namespace);
|
|
self.emit_scope_thunks(thunks);
|
|
self.emit_expr(body);
|
|
self.emit_op(Op::PopWith);
|
|
}
|
|
|
|
fn emit_toplevel_inner(&mut self, body: RawIrRef<'_>, thunks: &[(ThunkId, RawIrRef<'_>)]) {
|
|
self.emit_scope_thunks(thunks);
|
|
self.emit_expr(body);
|
|
}
|
|
}
|