forked from MightyPork/crsn
add (cas), (casXX), and (bfcas)
This commit is contained in:
@@ -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
|
; Offsets can be specified to work with arbitrary bit slices
|
||||||
(xchXX RW:offset RW:offset)
|
(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
|
; Store status flags to a register
|
||||||
(stf Wr)
|
(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
|
; Remove item at a position, shifting the rest to the left to fill the empty space
|
||||||
(bfrm Wr @Obj Rd:index)
|
(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:
|
Whole buffer manipulation:
|
||||||
|
|||||||
@@ -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<Option<(RdWr, Rd, Rd, BitMask)>, CrsnError> {
|
||||||
|
if let Some(s) = keyword.strip_prefix(prefix) {
|
||||||
|
let width = if s.is_empty() {
|
||||||
|
(std::mem::size_of::<Value>() 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
|
/// Parse combining binary instruction operands (i.e. add) without masks
|
||||||
/// Accepts (Wr, Rd, Rd) and (RdWr, Rd)
|
/// Accepts (Wr, Rd, Rd) and (RdWr, Rd)
|
||||||
pub fn parse_wr_rd_rd(&mut self) -> Result<(Wr, Rd, Rd), CrsnError> {
|
pub fn parse_wr_rd_rd(&mut self) -> Result<(Wr, Rd, Rd), CrsnError> {
|
||||||
|
|||||||
@@ -164,6 +164,8 @@ pub enum BuiltinOp {
|
|||||||
LoadSequence { dst: Wr, value: LdsValue },
|
LoadSequence { dst: Wr, value: LdsValue },
|
||||||
/// Swap two registers
|
/// Swap two registers
|
||||||
Exchange { a: RdWr, b: RdWr, mask: BitMask },
|
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
|
/// Store runtime status to a register
|
||||||
StoreFlags { dst: Wr },
|
StoreFlags { dst: Wr },
|
||||||
/// Load runtime status from a register
|
/// Load runtime status from a register
|
||||||
|
|||||||
@@ -173,6 +173,28 @@ impl OpTrait for BuiltinOp {
|
|||||||
state.write(b, bb2)?;
|
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 } => {
|
BuiltinOp::StoreFlags { dst } => {
|
||||||
let packed = state.frame.status.store();
|
let packed = state.frame.status.store();
|
||||||
state.write(dst, packed)?;
|
state.write(dst, packed)?;
|
||||||
|
|||||||
@@ -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 }));
|
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(':') {
|
if other.starts_with(':') {
|
||||||
BuiltinOp::Label(parse_label_str(other, &op_pos)?)
|
BuiltinOp::Label(parse_label_str(other, &op_pos)?)
|
||||||
} else {
|
} 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)])
|
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::StoreFlags { dst } => sexp::list(&[A("stf"), A(dst)]),
|
||||||
BuiltinOp::LoadFlags { src } => sexp::list(&[A("ldf"), A(src)]),
|
BuiltinOp::LoadFlags { src } => sexp::list(&[A("ldf"), A(src)]),
|
||||||
BuiltinOp::LoadSequence { dst, value } => {
|
BuiltinOp::LoadSequence { dst, value } => {
|
||||||
@@ -474,6 +485,8 @@ mod test {
|
|||||||
("(xch r0 r1)", "(xch r0 r1)"),
|
("(xch r0 r1)", "(xch r0 r1)"),
|
||||||
("(xch32 r0 r1)", "(xch32 r0 r1)"),
|
("(xch32 r0 r1)", "(xch32 r0 r1)"),
|
||||||
("(xch32 r0:8 r1:16)", "(xch32 r0:8 r1:16)"),
|
("(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)"),
|
("(ld r0 r0)", "(ld r0 r0)"),
|
||||||
("(ld8 r0 r1)", "(ld8 r0 r1)"),
|
("(ld8 r0 r1)", "(ld8 r0 r1)"),
|
||||||
("(ld16 r0 r1)", "(ld16 r0 r1)"),
|
("(ld16 r0 r1)", "(ld16 r0 r1)"),
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ pub enum BufOps {
|
|||||||
Write { obj: RdObj, idx: Rd, value: Rd },
|
Write { obj: RdObj, idx: Rd, value: Rd },
|
||||||
Insert { obj: RdObj, idx: Rd, value: Rd },
|
Insert { obj: RdObj, idx: Rd, value: Rd },
|
||||||
Remove { dst: Wr, obj: RdObj, idx: Rd },
|
Remove { dst: Wr, obj: RdObj, idx: Rd },
|
||||||
|
CompareSwap { obj: RdObj, idx: Rd, expected: Rd, src: Rd },
|
||||||
|
|
||||||
// Whole buffer ops
|
// Whole buffer ops
|
||||||
Resize { obj: RdObj, len: Rd },
|
Resize { obj: RdObj, len: Rd },
|
||||||
|
|||||||
@@ -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 } => {
|
BufOps::Insert { obj, idx, value } => {
|
||||||
state.clear_status();
|
state.clear_status();
|
||||||
let handle = state.read_obj(obj)?;
|
let handle = state.read_obj(obj)?;
|
||||||
@@ -255,6 +282,10 @@ impl OpTrait for BufOps {
|
|||||||
sexp::list(&[A("bfwr"), A(obj), A(idx), A(value)])
|
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 } => {
|
BufOps::Insert { obj, idx, value } => {
|
||||||
sexp::list(&[A("bfins"), A(obj), A(idx), A(value)])
|
sexp::list(&[A("bfins"), A(obj), A(idx), A(value)])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,14 @@ pub(crate) fn parse<'a>(_pos: &SourcePosition, keyword: &str, mut args: TokenPar
|
|||||||
BufOps::Write { obj, idx, value }
|
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" => {
|
"bfsz" => {
|
||||||
let dst = args.next_wr()?;
|
let dst = args.next_wr()?;
|
||||||
let obj = args.next_rdobj()?;
|
let obj = args.next_rdobj()?;
|
||||||
|
|||||||
Reference in New Issue
Block a user