diff --git a/README.md b/README.md index ad487a0..2358805 100644 --- a/README.md +++ b/README.md @@ -377,6 +377,18 @@ Jumping to a label is always safer than a manual skip. ; Offsets can be specified to work with arbitrary bit slices (xchXX RW:offset RW:offset) +; Compare and swap; atomic instruction for coroutine synchronization +; - The "Equal" flag is set on success, use it with conditional branches or conditional execution. +; - The new value is not read until the comparison passes and it is needed. +; This behavior may matter for side effects when used with object handles. +(cas RW Rd'expected Rd'new) + +; Compare and swap a bit slice; atomic instruction for coroutine synchronization +; See (cas) above for more info. +; +; Offsets can be specified to work with arbitrary bit slices +(casXX RW:offset Rd:offset'expected Rd:offset'new) + ; Store status flags to a register (stf Wr) @@ -735,6 +747,17 @@ Primitive buffer ops (position is always 0-based) ; Remove item at a position, shifting the rest to the left to fill the empty space (bfrm Wr @Obj Rd:index) + +; Buffer value compare and swap; atomic instruction for coroutine synchronization. +; - The "Equal" flag is set on success, use it with conditional branches or conditional execution. +; - The new value is not read until the comparison passes and it is needed. +; This behavior may matter for side effects when used with object handles. +; +; This instruction is useful when more than one lock is needed and they are stored in a buffer at well known positions. +; Naturally, the buffer must not be mutated in other ways that would undermine the locking. +; +; If an index just outside the buffer is used, the value is read as zero the position is created (if zero was expected). +(bfcas @Obj Rd:index Rd'expected Rd'new) ``` Whole buffer manipulation: diff --git a/crsn/src/asm/parse/arg_parser.rs b/crsn/src/asm/parse/arg_parser.rs index eef11aa..2b0a6e0 100644 --- a/crsn/src/asm/parse/arg_parser.rs +++ b/crsn/src/asm/parse/arg_parser.rs @@ -291,6 +291,36 @@ impl<'a> TokenParser<'a> { } } + /// Parse (RdWr, Rd, Rd) with an optional mask + pub fn parse_masked_rdwr_rd_rd(&mut self, keyword: &str, prefix: &str) -> Result, CrsnError> { + if let Some(s) = keyword.strip_prefix(prefix) { + let width = if s.is_empty() { + (std::mem::size_of::() as u32) * 8 + } else { + s.parse().err_pos(self.start_pos)? + }; + if self.len() == 3 { + let (rw, dst_pos) = self.next_rdwr_offset()?; + let (r1, src_pos) = self.next_rd_offset()?; + let (r2, src2_pos) = self.next_rd_offset()?; + + let mask = BitMask { + width, + dst_pos, + src_pos, + src2_pos, + }; + mask.validate(self.start_pos)?; + + Ok(Some((rw, r1, r2, mask))) + } else { + Err(CrsnError::Parse("Instruction needs 3 (RW,Rd,Rd) arguments!".into(), self.start_pos.clone())) + } + } else { + Ok(None) + } + } + /// Parse combining binary instruction operands (i.e. add) without masks /// Accepts (Wr, Rd, Rd) and (RdWr, Rd) pub fn parse_wr_rd_rd(&mut self) -> Result<(Wr, Rd, Rd), CrsnError> { diff --git a/crsn/src/builtin/defs.rs b/crsn/src/builtin/defs.rs index 4681131..0e69f6b 100644 --- a/crsn/src/builtin/defs.rs +++ b/crsn/src/builtin/defs.rs @@ -164,6 +164,8 @@ pub enum BuiltinOp { LoadSequence { dst: Wr, value: LdsValue }, /// Swap two registers Exchange { a: RdWr, b: RdWr, mask: BitMask }, + /// Swap if equal to a pattern + CompareSwap { dst: RdWr, expected: Rd, src: Rd, mask: BitMask }, /// Store runtime status to a register StoreFlags { dst: Wr }, /// Load runtime status from a register diff --git a/crsn/src/builtin/exec.rs b/crsn/src/builtin/exec.rs index 34f9c83..535ba1a 100644 --- a/crsn/src/builtin/exec.rs +++ b/crsn/src/builtin/exec.rs @@ -173,6 +173,28 @@ impl OpTrait for BuiltinOp { state.write(b, bb2)?; } } + BuiltinOp::CompareSwap { dst, expected, src, mask: slice } => { + state.clear_status(); + let old = state.read(dst)?; + let exp = state.read(expected)?; + + if slice.is_full() { + if old == exp { + let new = state.read(src)?; + state.write(dst, new)?; + state.set_flag(Flag::Equal, true); + } + } else { + let ones: u64 = (1 << slice.width) - 1; + + if (old & (ones << slice.dst_pos)) == (exp & (ones << slice.src_pos)) { + let new = state.read(src)?; + let replacement = (old & !(ones << slice.dst_pos)) | (((new & (ones << slice.src2_pos)) >> slice.src2_pos) << slice.dst_pos); + state.write(dst, replacement)?; + state.set_flag(Flag::Equal, true); + } + } + } BuiltinOp::StoreFlags { dst } => { let packed = state.frame.status.store(); state.write(dst, packed)?; diff --git a/crsn/src/builtin/parse.rs b/crsn/src/builtin/parse.rs index 1e9fd18..a1563c6 100644 --- a/crsn/src/builtin/parse.rs +++ b/crsn/src/builtin/parse.rs @@ -289,6 +289,10 @@ pub(crate) fn parse_op<'a>(op_pos: &SourcePosition, keyword: &str, mut args: Tok return Ok(ParseRes::builtin(BuiltinOp::Exchange { a, b, mask })); } + if let Some((dst, expected, src, mask)) = args.parse_masked_rdwr_rd_rd(other, "cas")? { + return Ok(ParseRes::builtin(BuiltinOp::CompareSwap { dst, expected, src, mask })); + } + if other.starts_with(':') { BuiltinOp::Label(parse_label_str(other, &op_pos)?) } else { @@ -389,6 +393,13 @@ pub(crate) fn to_sexp(op: &BuiltinOp) -> Sexp { sexp::list(&[A(format!("xch{}", mask.width)), AM(a, mask.dst_pos), AM(b, mask.src_pos)]) } }, + BuiltinOp::CompareSwap { dst, expected, src, mask } => { + if mask.is_full() { + sexp::list(&[A("cas"), A(dst), A(expected), A(src)]) + } else { + sexp::list(&[A(format!("cas{}", mask.width)), AM(dst, mask.dst_pos), AM(expected, mask.src_pos), AM(src, mask.src2_pos)]) + } + }, BuiltinOp::StoreFlags { dst } => sexp::list(&[A("stf"), A(dst)]), BuiltinOp::LoadFlags { src } => sexp::list(&[A("ldf"), A(src)]), BuiltinOp::LoadSequence { dst, value } => { @@ -474,6 +485,8 @@ mod test { ("(xch r0 r1)", "(xch r0 r1)"), ("(xch32 r0 r1)", "(xch32 r0 r1)"), ("(xch32 r0:8 r1:16)", "(xch32 r0:8 r1:16)"), + ("(cas r0 r1 r2)", "(cas r0 r1 r2)"), + ("(cas8 r0:8 r1:16 r2:24)", "(cas8 r0:8 r1:16 r2:24)"), ("(ld r0 r0)", "(ld r0 r0)"), ("(ld8 r0 r1)", "(ld8 r0 r1)"), ("(ld16 r0 r1)", "(ld16 r0 r1)"), diff --git a/crsn_buf/src/defs.rs b/crsn_buf/src/defs.rs index 19973fa..d158e15 100644 --- a/crsn_buf/src/defs.rs +++ b/crsn_buf/src/defs.rs @@ -45,6 +45,7 @@ pub enum BufOps { Write { obj: RdObj, idx: Rd, value: Rd }, Insert { obj: RdObj, idx: Rd, value: Rd }, Remove { dst: Wr, obj: RdObj, idx: Rd }, + CompareSwap { obj: RdObj, idx: Rd, expected: Rd, src: Rd }, // Whole buffer ops Resize { obj: RdObj, len: Rd }, diff --git a/crsn_buf/src/exec.rs b/crsn_buf/src/exec.rs index a22e1bc..07e716d 100644 --- a/crsn_buf/src/exec.rs +++ b/crsn_buf/src/exec.rs @@ -138,6 +138,33 @@ impl OpTrait for BufOps { } } + BufOps::CompareSwap { obj, idx, expected, src } => { + state.clear_status(); + let handle = state.read_obj(obj)?; + let idx = state.read(idx)? as usize; + let expected = state.read(expected)?; + let store: &mut ExtData = state.ext_mut(); + let buf = store.buffers.get_mut(&handle).ok_or_else(|| Fault::ObjectNotExist(handle))?; + if idx < buf.data.len() { + if buf.data[idx] == expected { + // This looks stupid and it is stupid - the dance is needed to satisfy the borrow checker. + let val = state.read(src)?; + let store: &mut ExtData = state.ext_mut(); + let buf = store.buffers.get_mut(&handle).unwrap(); + buf.data[idx] = val; + state.set_flag(Flag::Equal, true); + } + } else if idx == buf.data.len() && expected == 0 { + let val = state.read(src)?; + let store: &mut ExtData = state.ext_mut(); + let buf = store.buffers.get_mut(&handle).unwrap(); + buf.data.push_back(val); + state.set_flag(Flag::Equal, true); + } else { + state.set_flag(Flag::Overflow, true); + } + } + BufOps::Insert { obj, idx, value } => { state.clear_status(); let handle = state.read_obj(obj)?; @@ -255,6 +282,10 @@ impl OpTrait for BufOps { sexp::list(&[A("bfwr"), A(obj), A(idx), A(value)]) } + BufOps::CompareSwap { obj, idx, expected , src } => { + sexp::list(&[A("bfcas"), A(obj), A(idx), A(expected), A(src)]) + } + BufOps::Insert { obj, idx, value } => { sexp::list(&[A("bfins"), A(obj), A(idx), A(value)]) } diff --git a/crsn_buf/src/parse.rs b/crsn_buf/src/parse.rs index 209967b..451301a 100644 --- a/crsn_buf/src/parse.rs +++ b/crsn_buf/src/parse.rs @@ -75,6 +75,14 @@ pub(crate) fn parse<'a>(_pos: &SourcePosition, keyword: &str, mut args: TokenPar BufOps::Write { obj, idx, value } } + "bfcas" => { + let obj = args.next_rdobj()?; + let idx = args.next_rd()?; + let expected = args.next_rd()?; + let src = args.next_rd()?; + BufOps::CompareSwap { obj, idx, expected, src } + } + "bfsz" => { let dst = args.next_wr()?; let obj = args.next_rdobj()?;