Compare commits

..

2 Commits

Author SHA1 Message Date
imxyy1soope1 66d1f30b5a temp 2026-04-30 22:01:53 +08:00
imxyy1soope1 260bea9ff1 implement pattern calling 2026-04-29 18:11:35 +08:00
10 changed files with 131 additions and 52 deletions
+3
View File
@@ -242,6 +242,9 @@ pub enum PrimOpPhase {
ForceResultShallowLoop, ForceResultShallowLoop,
ForceResultDeepFinish, ForceResultDeepFinish,
// TODO: split into separate enums
CallPattern,
Illegal, Illegal,
} }
+6 -9
View File
@@ -164,7 +164,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
} }
#[must_use] #[must_use]
fn inline_maybe_thunk(&mut self, val: MaybeThunk) -> InlineOperand { fn inline_maybe_thunk(&self, val: MaybeThunk) -> InlineOperand {
use MaybeThunk::*; use MaybeThunk::*;
match val { match val {
Int(x) => { Int(x) => {
@@ -812,14 +812,11 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
let span_id = self.ctx.register_span(span); let span_id = self.ctx.register_span(span);
self.emit_u32(span_id); self.emit_u32(span_id);
} }
for &(_key, _val, _span) in dyns.iter() { for &(_key, val, span) in dyns.iter() {
todo!("redesign dynamic attr key"); self.emit_u8(AttrKeyType::Dynamic as u8);
// self.emit_u8(AttrKeyType::Dynamic as u8); self.emit_maybe_thunk(val);
// self.emit_maybe_thunk(key); let span_id = self.ctx.register_span(span);
// let val_operand = self.inline_maybe_thunk(val); self.emit_u32(span_id);
// self.emit_maybe_thunk(val);
// let span_id = self.ctx.register_span(span);
// self.emit_u32(span_id);
} }
} }
+4 -4
View File
@@ -3,7 +3,7 @@ use fix_common::StringId;
use num_enum::TryFromPrimitive; use num_enum::TryFromPrimitive;
use string_interner::Symbol as _; use string_interner::Symbol as _;
use crate::{OperandData, VmRuntimeCtx}; use crate::{AttrKeyData, OperandData, VmRuntimeCtx};
pub(crate) struct BytecodeReader<'a> { pub(crate) struct BytecodeReader<'a> {
bytecode: &'a [u8], bytecode: &'a [u8],
@@ -117,14 +117,14 @@ impl<'a> BytecodeReader<'a> {
} }
#[inline(always)] #[inline(always)]
pub(crate) fn read_attr_key_data<C: VmRuntimeCtx>(&mut self, ctx: &C) -> crate::AttrKeyData { pub(crate) fn read_attr_key_data(&mut self) -> crate::AttrKeyData {
use fix_codegen::AttrKeyType; use fix_codegen::AttrKeyType;
let tag = self.read_u8(); let tag = self.read_u8();
let ty = AttrKeyType::try_from_primitive(tag) let ty = AttrKeyType::try_from_primitive(tag)
.unwrap_or_else(|err| panic!("unknown key tag: {:#04x}", err.number)); .unwrap_or_else(|err| panic!("unknown key tag: {:#04x}", err.number));
match ty { match ty {
AttrKeyType::Static => crate::AttrKeyData::Static(self.read_string_id()), AttrKeyType::Static => AttrKeyData::Static(self.read_string_id()),
AttrKeyType::Dynamic => crate::AttrKeyData::Dynamic(self.read_operand_data(ctx)), AttrKeyType::Dynamic => AttrKeyData::Dynamic,
} }
} }
+2
View File
@@ -249,6 +249,8 @@ impl<'gc> crate::Vm<'gc> {
return Ok(true); return Ok(true);
} }
if let (Some(a), Some(b)) = (lhs.as_gc::<crate::AttrSet>(), rhs.as_gc::<crate::AttrSet>()) { if let (Some(a), Some(b)) = (lhs.as_gc::<crate::AttrSet>(), rhs.as_gc::<crate::AttrSet>()) {
let a = a.entries.borrow();
let b = b.entries.borrow();
if a.len() != b.len() { if a.len() != b.len() {
return Ok(false); return Ok(false);
} }
+57 -3
View File
@@ -1,6 +1,6 @@
use fix_builtins::PrimOpPhase; use fix_builtins::PrimOpPhase;
use fix_error::Error; use fix_error::Error;
use gc_arena::{Gc, Mutation}; use gc_arena::{Gc, Mutation, RefLock};
use num_enum::TryFromPrimitive as _; use num_enum::TryFromPrimitive as _;
use smallvec::SmallVec; use smallvec::SmallVec;
@@ -37,6 +37,8 @@ impl<'gc> Vm<'gc> {
ForceResultShallowLoop => self.primop_force_result_shallow_loop(reader, mc), ForceResultShallowLoop => self.primop_force_result_shallow_loop(reader, mc),
ForceResultDeepFinish => self.primop_force_result_deep_finish(ctx, reader, mc), ForceResultDeepFinish => self.primop_force_result_deep_finish(ctx, reader, mc),
CallPattern => self.primop_call_pattern(ctx, reader, mc),
phase => todo!("primop phase {phase:?}"), phase => todo!("primop phase {phase:?}"),
} }
} }
@@ -163,6 +165,7 @@ impl<'gc> Vm<'gc> {
let e1 = self.peek_forced(1); let e1 = self.peek_forced(1);
let children: SmallVec<_> = if let Some(attrs) = e1.as_gc::<AttrSet>() { let children: SmallVec<_> = if let Some(attrs) = e1.as_gc::<AttrSet>() {
let attrs = attrs.entries.borrow();
if attrs.is_empty() { if attrs.is_empty() {
SmallVec::new() SmallVec::new()
} else { } else {
@@ -240,6 +243,7 @@ impl<'gc> Vm<'gc> {
let mut added: usize = 0; let mut added: usize = 0;
if let Some(attrs) = item.as_gc::<AttrSet>() { if let Some(attrs) = item.as_gc::<AttrSet>() {
let attrs = attrs.entries.borrow();
#[allow(clippy::unwrap_used)] #[allow(clippy::unwrap_used)]
let seen = self.peek_forced(2).as_gc::<List<'gc>>().unwrap(); let seen = self.peek_forced(2).as_gc::<List<'gc>>().unwrap();
if !self.is_value_in_seen(seen, item) { if !self.is_value_in_seen(seen, item) {
@@ -287,7 +291,7 @@ impl<'gc> Vm<'gc> {
let val = self.peek_forced(0); let val = self.peek_forced(0);
let (count, has_children) = if let Some(attrs) = val.as_gc::<AttrSet>() { let (count, has_children) = if let Some(attrs) = val.as_gc::<AttrSet>() {
let len = attrs.len(); let len = attrs.entries.borrow().len();
(len, len > 0) (len, len > 0)
} else if let Some(list) = val.as_gc::<List<'gc>>() { } else if let Some(list) = val.as_gc::<List<'gc>>() {
let len = list.inner.borrow().len(); let len = list.inner.borrow().len();
@@ -327,7 +331,7 @@ impl<'gc> Vm<'gc> {
let val = self.peek_forced(2); let val = self.peek_forced(2);
let child = if let Some(attrs) = val.as_gc::<AttrSet>() { let child = if let Some(attrs) = val.as_gc::<AttrSet>() {
attrs.get(idx as usize).map(|&(_, v)| v) attrs.entries.borrow().get(idx as usize).map(|&(_, v)| v)
} else if let Some(list) = val.as_gc::<List<'gc>>() { } else if let Some(list) = val.as_gc::<List<'gc>>() {
list.inner.borrow().get(idx as usize).copied() list.inner.borrow().get(idx as usize).copied()
} else { } else {
@@ -368,6 +372,56 @@ impl<'gc> Vm<'gc> {
self.finish_ok(ctx.convert_value(val.relax())) self.finish_ok(ctx.convert_value(val.relax()))
} }
pub(crate) fn primop_call_pattern(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let (func, attrset) = self.try_force::<(Gc<Closure>, Gc<AttrSet>)>(reader, mc)?;
let Closure {
ip,
n_locals,
env,
pattern,
} = *func;
let Some(pattern) = pattern else {
unreachable!()
};
// TODO: get function name
// TODO: param spans
if !pattern.ellipsis {
for key in pattern.required.iter().copied() {
if attrset.lookup(key).is_none() {
let name = ctx.resolve_string(key);
return self.finish_err(Error::eval_error(format!(
"function 'anonymous lambda' called without required argument '{name}'"
)));
}
}
for &(key, _) in attrset.entries.borrow().iter() {
let is_expected =
pattern.required.contains(&key) || pattern.optional.contains(&key);
if !is_expected {
let name = ctx.resolve_string(key);
return self.finish_err(Error::eval_error(format!(
"function 'anonymous lambda' called with unexpected argument '{name}'"
)));
}
}
}
let new_env = Gc::new(
mc,
RefLock::new(Env::with_arg(Value::new_gc(attrset), n_locals, env)),
);
reader.set_pc(ip as usize);
self.env = new_env;
Step::Continue(())
}
fn is_value_in_seen(&self, seen: Gc<'gc, List<'gc>>, val: Value<'gc>) -> bool { fn is_value_in_seen(&self, seen: Gc<'gc, List<'gc>>, val: Value<'gc>) -> bool {
if !is_container(val) { if !is_container(val) {
return false; return false;
+20 -9
View File
@@ -23,13 +23,10 @@ impl<'gc> crate::Vm<'gc> {
} }
self.call_depth += 1; self.call_depth += 1;
if let Some(closure) = func.as_gc::<Closure>() { if let Some(closure) = func.as_gc::<Closure>() {
let ip = closure.ip; if closure.pattern.is_some() {
let n_locals = closure.n_locals; // FIXME: better DX...
let env = closure.env; self.push(func.relax());
if let Some(ref _pattern) = closure.pattern { self.push(arg);
todo!("pattern call")
} else {
let new_env = Gc::new(mc, RefLock::new(Env::with_arg(arg, n_locals, env)));
self.call_stack.push(CallFrame { self.call_stack.push(CallFrame {
pc: resume_pc, pc: resume_pc,
stack_depth: 0, stack_depth: 0,
@@ -37,9 +34,23 @@ impl<'gc> crate::Vm<'gc> {
env: self.env, env: self.env,
with_env: self.with_env, with_env: self.with_env,
}); });
reader.set_pc(ip as usize); reader.set_pc(PrimOpPhase::CallPattern.ip() as usize);
self.env = new_env; return Step::Continue(());
} }
let ip = closure.ip;
let n_locals = closure.n_locals;
let env = closure.env;
let new_env = Gc::new(mc, RefLock::new(Env::with_arg(arg, n_locals, env)));
self.call_stack.push(CallFrame {
pc: resume_pc,
stack_depth: 0,
thunk: None,
env: self.env,
with_env: self.with_env,
});
reader.set_pc(ip as usize);
self.env = new_env;
} else if let Some(primop) = func.as_inline::<PrimOp>() { } else if let Some(primop) = func.as_inline::<PrimOp>() {
if primop.arity == 1 { if primop.arity == 1 {
self.push(arg); self.push(arg);
+3 -5
View File
@@ -20,7 +20,7 @@ impl<'gc> crate::Vm<'gc> {
let count = reader.read_u32() as usize; let count = reader.read_u32() as usize;
let mut entries: SmallVec<[AttrEntry; 4]> = SmallVec::with_capacity(count); let mut entries: SmallVec<[AttrEntry; 4]> = SmallVec::with_capacity(count);
for _ in 0..count { for _ in 0..count {
let key = reader.read_attr_key_data(ctx); let key = reader.read_attr_key_data();
let val = reader.read_operand_data(ctx); let val = reader.read_operand_data(ctx);
let _span_id = reader.read_u32(); let _span_id = reader.read_u32();
entries.push(AttrEntry { key, val }); entries.push(AttrEntry { key, val });
@@ -29,10 +29,8 @@ impl<'gc> crate::Vm<'gc> {
for entry in &entries { for entry in &entries {
let key_sid = match &entry.key { let key_sid = match &entry.key {
AttrKeyData::Static(sid) => *sid, AttrKeyData::Static(sid) => *sid,
AttrKeyData::Dynamic(op) => { AttrKeyData::Dynamic => {
let v = op.resolve(mc, self); todo!()
v.as_inline::<crate::StringId>()
.expect("dynamic attr key must be a string")
} }
}; };
let val = entry.val.resolve(mc, self); let val = entry.val.resolve(mc, self);
+3 -3
View File
@@ -143,14 +143,14 @@ impl<T: VmRuntimeCtx> ConvertValueWithSeen for T {
Value::String(ns.as_str().to_owned()) Value::String(ns.as_str().to_owned())
} else if let Some(attrs) = val.as_gc::<AttrSet>() { } else if let Some(attrs) = val.as_gc::<AttrSet>() {
let bits = val.to_bits(); let bits = val.to_bits();
if attrs.is_empty() { if attrs.entries.borrow().is_empty() {
return Value::AttrSet(Default::default()); return Value::AttrSet(Default::default());
} }
if !seen.insert(bits) { if !seen.insert(bits) {
return Value::Repeated; return Value::Repeated;
} }
let mut map = std::collections::BTreeMap::new(); let mut map = std::collections::BTreeMap::new();
for &(key, val) in attrs.iter() { for &(key, val) in attrs.entries.borrow().iter() {
let key = self.resolve_string(key).to_owned(); let key = self.resolve_string(key).to_owned();
let converted = self.convert_value_with_seen(val, seen); let converted = self.convert_value_with_seen(val, seen);
map.insert(fix_common::Symbol::from(key), converted); map.insert(fix_common::Symbol::from(key), converted);
@@ -252,7 +252,7 @@ impl OperandData {
pub(crate) enum AttrKeyData { pub(crate) enum AttrKeyData {
Static(StringId), Static(StringId),
Dynamic(OperandData), Dynamic,
} }
fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmRuntimeCtx) -> Value<'gc> { fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmRuntimeCtx) -> Value<'gc> {
+32 -19
View File
@@ -435,65 +435,78 @@ impl fmt::Debug for NixString {
#[derive(Collect, Debug, Default)] #[derive(Collect, Debug, Default)]
#[collect(no_drop)] #[collect(no_drop)]
pub(crate) struct AttrSet<'gc> { pub(crate) struct AttrSet<'gc> {
entries: SmallVec<[(StringId, Value<'gc>); 4]>, pub(crate) entries: RefLock<SmallVec<[(StringId, Value<'gc>); 4]>>,
} }
impl<'gc> Deref for AttrSet<'gc> { impl<'gc> Unlock for AttrSet<'gc> {
type Target = [(StringId, Value<'gc>)]; type Unlocked = RefCell<SmallVec<[(StringId, Value<'gc>); 4]>>;
fn deref(&self) -> &Self::Target { unsafe fn unlock_unchecked(&self) -> &Self::Unlocked {
&self.entries unsafe { self.entries.unlock_unchecked() }
} }
} }
impl<'gc> AttrSet<'gc> { impl<'gc> AttrSet<'gc> {
pub(crate) fn from_sorted_unchecked(entries: SmallVec<[(StringId, Value<'gc>); 4]>) -> Self { pub(crate) fn from_sorted_unchecked(entries: SmallVec<[(StringId, Value<'gc>); 4]>) -> Self {
debug_assert!(entries.is_sorted_by_key(|(key, _)| *key)); debug_assert!(entries.is_sorted_by_key(|(key, _)| *key));
Self { entries } Self {
entries: RefLock::new(entries),
}
} }
pub(crate) fn lookup(&self, key: StringId) -> Option<Value<'gc>> { pub(crate) fn lookup(&self, key: StringId) -> Option<Value<'gc>> {
self.entries let entries = self.entries.borrow();
entries
.binary_search_by_key(&key, |(k, _)| *k) .binary_search_by_key(&key, |(k, _)| *k)
.ok() .ok()
.map(|i| self.entries[i].1) .map(|i| entries[i].1)
} }
pub(crate) fn has(&self, key: StringId) -> bool { pub(crate) fn has(&self, key: StringId) -> bool {
self.entries.binary_search_by_key(&key, |(k, _)| *k).is_ok() self.entries
.borrow()
.binary_search_by_key(&key, |(k, _)| *k)
.is_ok()
} }
pub(crate) fn merge(&self, other: &Self, mc: &Mutation<'gc>) -> Gc<'gc, Self> { pub(crate) fn merge(&self, other: &Self, mc: &Mutation<'gc>) -> Gc<'gc, Self> {
use std::cmp::Ordering::*; use std::cmp::Ordering::*;
debug_assert!(self.entries.is_sorted_by_key(|(key, _)| *key)); let self_entries = self.entries.borrow();
debug_assert!(other.entries.is_sorted_by_key(|(key, _)| *key)); let other_entries = other.entries.borrow();
debug_assert!(self_entries.is_sorted_by_key(|(key, _)| *key));
debug_assert!(other_entries.is_sorted_by_key(|(key, _)| *key));
let mut entries = SmallVec::new(); let mut entries = SmallVec::new();
let mut i = 0; let mut i = 0;
let mut j = 0; let mut j = 0;
while i < self.entries.len() && j < other.entries.len() { while i < self_entries.len() && j < other_entries.len() {
match self.entries[i].0.cmp(&other.entries[j].0) { match self_entries[i].0.cmp(&other_entries[j].0) {
Less => { Less => {
entries.push(self.entries[i]); entries.push(self_entries[i]);
i += 1; i += 1;
} }
Greater => { Greater => {
entries.push(other.entries[j]); entries.push(other_entries[j]);
j += 1; j += 1;
} }
Equal => { Equal => {
entries.push(other.entries[j]); entries.push(other_entries[j]);
i += 1; i += 1;
j += 1; j += 1;
} }
} }
} }
entries.extend(other.entries[j..].iter().cloned()); entries.extend(other_entries[j..].iter().cloned());
entries.extend(self.entries[i..].iter().cloned()); entries.extend(self_entries[i..].iter().cloned());
debug_assert!(entries.is_sorted_by_key(|(key, _)| *key)); debug_assert!(entries.is_sorted_by_key(|(key, _)| *key));
Gc::new(mc, AttrSet { entries }) Gc::new(
mc,
AttrSet {
entries: RefLock::new(entries),
},
)
} }
} }
+1
View File
@@ -50,6 +50,7 @@
llm-agents.claude-code llm-agents.claude-code
llm-agents.opencode llm-agents.opencode
llm-agents.forge
]; ];
}; };
} }