-
Notifications
You must be signed in to change notification settings - Fork 58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rust-based Dialect api. #109
base: main
Are you sure you want to change the base?
Changes from all commits
ca503f6
2fa09f5
0eed276
8fc7222
e3df5c1
15e0c15
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
use std::collections::HashMap; | ||
|
||
use crate::allocator::{Allocator, NodePtr}; | ||
use crate::cost::Cost; | ||
use crate::dialect::Dialect; | ||
use crate::err_utils::err; | ||
use crate::f_table::{f_lookup_for_hashmap, FLookup}; | ||
use crate::more_ops::op_unknown; | ||
use crate::reduction::Response; | ||
use crate::run_program::OperatorHandler; | ||
|
||
const QUOTE_KW: [u8; 1] = [1]; | ||
const APPLY_KW: [u8; 1] = [2]; | ||
|
||
pub struct OperatorHandlerWithMode { | ||
f_lookup: FLookup, | ||
strict: bool, | ||
} | ||
|
||
impl OperatorHandlerWithMode { | ||
pub fn new_with_hashmap(hashmap: HashMap<String, Vec<u8>>, strict: bool) -> Self { | ||
let f_lookup: FLookup = f_lookup_for_hashmap(hashmap); | ||
OperatorHandlerWithMode { f_lookup, strict } | ||
} | ||
} | ||
|
||
impl OperatorHandler for OperatorHandlerWithMode { | ||
fn op( | ||
&self, | ||
allocator: &mut Allocator, | ||
o: NodePtr, | ||
argument_list: NodePtr, | ||
max_cost: Cost, | ||
) -> Response { | ||
let b = &allocator.atom(o); | ||
if b.len() == 1 { | ||
if let Some(f) = self.f_lookup[b[0] as usize] { | ||
return f(allocator, argument_list, max_cost); | ||
} | ||
} | ||
if self.strict { | ||
err(o, "unimplemented operator") | ||
} else { | ||
op_unknown(allocator, o, argument_list, max_cost) | ||
} | ||
} | ||
} | ||
|
||
pub fn chia_opcode_mapping() -> HashMap<String, Vec<u8>> { | ||
let mut h = HashMap::new(); | ||
let items = [ | ||
(3, "op_if"), | ||
(4, "op_cons"), | ||
(5, "op_first"), | ||
(6, "op_rest"), | ||
(7, "op_list"), | ||
(8, "op_raise"), | ||
(9, "op_eq"), | ||
(10, "op_gr_bytes"), | ||
(11, "op_sha256"), | ||
(12, "op_substr"), | ||
(13, "op_strlen"), | ||
(14, "op_concat"), | ||
(16, "op_add"), | ||
(17, "op_subtract"), | ||
(18, "op_multiply"), | ||
(19, "op_divide"), | ||
(20, "op_divmod"), | ||
(21, "op_greater"), | ||
(22, "op_ash"), | ||
(23, "op_lsh"), | ||
(24, "op_logand"), | ||
(25, "op_logior"), | ||
(26, "op_logxor"), | ||
(27, "op_lognot"), | ||
(29, "op_point_add"), | ||
(30, "op_pubkey_for_exp"), | ||
(32, "op_not"), | ||
(33, "op_any"), | ||
(34, "op_all"), | ||
(36, "op_softfork"), | ||
]; | ||
for (k, v) in items { | ||
h.insert(v.to_string(), [k as u8].into()); | ||
} | ||
h | ||
} | ||
|
||
pub fn chia_op_handler(strict: bool) -> OperatorHandlerWithMode { | ||
OperatorHandlerWithMode::new_with_hashmap(chia_opcode_mapping(), strict) | ||
} | ||
|
||
pub fn chia_dialect(strict: bool) -> Dialect { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as far as I can tell, this is the only function that needs to be public in this file. |
||
Dialect::new("E_KW, &APPLY_KW, Box::new(chia_op_handler(strict))) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
use crate::allocator::{Allocator, NodePtr}; | ||
use crate::cost::Cost; | ||
use crate::reduction::Response; | ||
use crate::run_program::{run_program, OperatorHandler, PreEval}; | ||
|
||
pub struct Dialect { | ||
quote_kw: Vec<u8>, | ||
apply_kw: Vec<u8>, | ||
op_handler: Box<dyn OperatorHandler>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see why this needs to be a run-time interface instead of a compile-time trait, not why it needs to be heap allocated. In the chia dialect case this class only contains one bool of state (strict mode).
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I could make a dialect trait, but we'd still need at least one concrete instance. Changing to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what I'm suggesting is that you can just rename the |
||
} | ||
|
||
impl OperatorHandler for Dialect { | ||
fn op( | ||
&self, | ||
allocator: &mut Allocator, | ||
op: NodePtr, | ||
args: NodePtr, | ||
max_cost: Cost, | ||
) -> Response { | ||
self.op_handler.op(allocator, op, args, max_cost) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. instead of forwarding this call, could the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For sure that'd be possible, but I don't immediately see that it would be any more efficient. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it would primarily be simpler, because you have a single interface instead of two. As far as I can tell, you'd never mix-n-match the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The I haven't benchmarked it, but I'd be surprised to see the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think the LTO would be able to do anything without further changes to code here https://github.com/Chia-Network/clvm_rs/blob/main/src/run_program.rs#L299 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I don't think you have to do it that way. Surely a trait can declare names of constants. These opcodes is known at compile time, so they don't need to be stored in variables. Functions would allow an implementation to keep them as run-time values though, but a
I would expect the our operator functions are so slow that they would dwarf the operator lookup, but once we make those faster I wouldn't be surprised this might be next. I would expect the branch prediction benefits of static calls, rather than indirect calls. I don't see the value in requiring to keep all this state in memory. As far as I can tell, it's more code and complexity. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
right, that shouldn't be a dynamic interface |
||
} | ||
} | ||
|
||
impl Dialect { | ||
pub fn new(quote_kw: &[u8], apply_kw: &[u8], op_handler: Box<dyn OperatorHandler>) -> Self { | ||
Dialect { | ||
quote_kw: quote_kw.to_owned(), | ||
apply_kw: apply_kw.to_owned(), | ||
op_handler, | ||
} | ||
} | ||
|
||
pub fn run_program_with_pre_eval( | ||
&self, | ||
allocator: &mut Allocator, | ||
program: NodePtr, | ||
args: NodePtr, | ||
max_cost: Cost, | ||
pre_eval: PreEval, | ||
) -> Response { | ||
run_program( | ||
allocator, | ||
program, | ||
args, | ||
&self.quote_kw, | ||
&self.apply_kw, | ||
max_cost, | ||
self, | ||
Some(pre_eval), | ||
) | ||
} | ||
|
||
pub fn run_program( | ||
&self, | ||
allocator: &mut Allocator, | ||
program: NodePtr, | ||
args: NodePtr, | ||
max_cost: Cost, | ||
) -> Response { | ||
run_program( | ||
allocator, | ||
program, | ||
args, | ||
&self.quote_kw, | ||
&self.apply_kw, | ||
max_cost, | ||
self, | ||
None, | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
mod allocator; | ||
pub mod chia_dialect; | ||
mod core_ops; | ||
mod cost; | ||
mod dialect; | ||
mod err_utils; | ||
mod f_table; | ||
mod gen; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this doesn't look like it belongs in the chia dialect. The chia dialect is known and doesn't need to be determined at run-time. It doesn't need to keep a run-time lookup-table. In fact, the run-time lookup-table seems like it belongs in the python binding, as it's a trick to allow mixing python functions with rust functions. In the rust API, there's no need for that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The lowest-level
run_program
implementation currently expects a thing with theOperatorHandler
trait, which defines the opcode-to-functionality map. Except forstrict
, this is pretty much the simplest implementation of that (FLookup
is just an array of 256 optional function pointers).I can move this to its own file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not as simple as this, which is stateless: https://github.com/Chia-Network/clvm_rs/pull/107/files#diff-4f84c6fa87ad47416016af253147484a8eef4a052dd794d4fd144db78865a172R33