add (cas), (casXX), and (bfcas)

coroutines
Ondřej Hruška 4 years ago
parent 4e67ac291f
commit d0d4f1c15e
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 23
      README.md
  2. 30
      crsn/src/asm/parse/arg_parser.rs
  3. 2
      crsn/src/builtin/defs.rs
  4. 22
      crsn/src/builtin/exec.rs
  5. 13
      crsn/src/builtin/parse.rs
  6. 1
      crsn_buf/src/defs.rs
  7. 31
      crsn_buf/src/exec.rs
  8. 8
      crsn_buf/src/parse.rs

@ -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()?;

Loading…
Cancel
Save