Skip to content

Conversation

xxuejie
Copy link
Collaborator

@xxuejie xxuejie commented Mar 21, 2025

Previously, all different machines in ckb-vm might use slightly different initialization process. This commit introduces a new DefaultMachineRunner trait, so the same initialization & usage workflow can be used for all types implementing this trait.

This is a cherry-pick of #469 to the develop branch. 2 more changes are applied on the develop branch:

  • Since AsmCoreMachine is now constant, we can simply use AsmCoreMachine instead of Box<AsmCoreMachine>.
  • refactor: Remove initializers from Memory trait #343 was mostly reverted. Upon more thinking, I think the change earlier does not make sense now. It's better we provide unified initialization for the whole machine, not just for the memory part.

Due to the new InstDecoder / TraceDecoder structure introduced in develop branch, more changes are required in the develop branch than 0.24.x releases. Some of those changes include:

  • Assembly machines now require TraceDecoder instead of InstDecoder. This means we cannot use DefaultMachineBuilder to build DefaultMachine structures for both types of VMs. The assembly machine types require more constraints. Given this consideration, DefaultMachineBuilder is removed in this commit. 2 new types RustDefaultMachineBuilder and AsmDefaultMachineBuilder are added for 2 types of VMs respectively.
  • A new set of types that begin with Abstract, such as AbstractDefaultMachineBuilder, AbstractAsmMachine, AbstractTraceMachine are introduced. One is only expected to use those types when one wants to tweak the inst/trace decoder used. Otherwise, one is safe to pick AsmMachine/AsmDefaultMachineBuilder or TraceMachine/RustDefaultMachineBuilder.

So the current workflow is like following:

  1. Pick a type implementing DefaultMachineRunner traits. Most likely, one would pick either AsmMachine or TraceMachine.
  2. Instantiate a core machine using DefaultMachineRunner::Inner::new.
  3. Based on the type picked in step 1, use AsmDefaultMachineBuilder or RustDefaultMachineBuilder to build a default machine with possible instruction cycle func, syscalls and debugger plugin.
  4. Finally, use DefaultMachineRunner::new to create the final runnable machine from the default machine.

For advanced usages where the decoder is customized, one can use AbstractAsmMachine or AbstractTraceMachine type. Then use AbstractDefaultMachineBuilder to build the default machine. And finally create the runnable machine. All those types accept an additional InstDecoder / TraceDecoder trait impl that allows one to customize the decoder for optimizations.

@xxuejie xxuejie requested review from quake and mohanson March 21, 2025 08:21
Previously, all different machines in ckb-vm might use slightly
different initialization process. This commit introduces a new
DefaultMachineRunner trait, so the same initialization & usage workflow
can be used for all types implementing this trait.

This is a cherry-pick of nervosnetwork#469
to the develop branch. 2 more changes are applied on the develop branch:

* Since AsmCoreMachine is now constant, we can simply use AsmCoreMachine
instead of `Box<AsmCoreMachine>`.
* nervosnetwork#343 was mostly reverted. Upon
more thinking, I think the change earlier does not make sense now. It's better
we provide unified initialization for the whole machine, not just for the memory
part.

Due to the new InstDecoder / TraceDecoder structure introduced in develop
branch, more changes are required in the develop branch than 0.24.x releases.
Some of those changes include:

* Assembly machines now require TraceDecoder instead of InstDecoder. This means
we cannot use DefaultMachineBuilder to build DefaultMachine structures for both
types of VMs. The assembly machine types require more constraints. Given this
consideration, DefaultMachineBuilder is removed in this commit. 2 new types
RustDefaultMachineBuilder and AsmDefaultMachineBuilder are added for 2 types of
VMs respectively.
* A new set of types that begin with `Abstract`, such as AbstractDefaultMachineBuilder,
AbstractAsmMachine, AbstractTraceMachine are introduced. One is only expected to
use those types when one wants to tweak the inst/trace decoder used. Otherwise,
one is safe to pick AsmMachine/AsmDefaultMachineBuilder or TraceMachine/RustDefaultMachineBuilder.

So the current workflow is like following:

1. Pick a type implementing DefaultMachineRunner traits. Most likely, one would
pick either AsmMachine or TraceMachine.
2. Instantiate a core machine using DefaultMachineRunner::Inner::new.
3. Based on the type picked in step 1, use AsmDefaultMachineBuilder or
RustDefaultMachineBuilder to build a default machine with possible instruction
cycle func, syscalls and debugger plugin.
4. Finally, use DefaultMachineRunner::new to create the final runnable machine
from the default machine.

For advanced usages where the decoder is customized, one can use AbstractAsmMachine
or AbstractTraceMachine type. Then use AbstractDefaultMachineBuilder
to build the default machine. And finally create the runnable machine. All those
types accept an additional InstDecoder / TraceDecoder trait impl that allows one
to customize the decoder for optimizations.
@xxuejie xxuejie force-pushed the new-initialization-trait-develop branch from 8c46b7a to 7b92b7c Compare March 21, 2025 08:23
mohanson
mohanson previously approved these changes Mar 24, 2025
Copy link
Member

@quake quake left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think introducing another TraceMachineRuner to extend DefaultMachineRunner trait would be a lot simpler than the current associated type design:

trait DefaultMachineRunner {
    fn run_with_decoder<D: InstDecoder>(&mut self, decoder: &mut D);
}

trait TraceMachineRunner: DefaultMachineRunner {
    fn run_with_decoder<D: TraceDecoder>(&mut self, decoder: &mut D);
}

trait InstDecoder {}
trait TraceDecoder: InstDecoder {}

This allows us to remove the Decoder generic type bound from most structs, for example, AbstractTraceMachine:

pub struct AbstractTraceMachine<Inner: SupportMachine> {
    pub machine: DefaultMachine<Inner>,
    factory: ThreadFactory<DefaultMachine<Inner>>,
    traces: Vec<Trace<DefaultMachine<Inner>>>,
}

@xxuejie
Copy link
Collaborator Author

xxuejie commented Mar 24, 2025

I think introducing another TraceMachineRuner to extend DefaultMachineRunner trait would be a lot simpler than the current associated type design

This would actually require specifying types when calling the method, see this example. In fact a lot of the work done in this PR, is done to make sure we don't need those extra work for specifying types. I do want to design the API that when you invoke run on AsmMachine, it will run on a TraceDecoder automatically. And it really does not make sense to call AsmMachine with a InstDecoder.

@quake
Copy link
Member

quake commented Mar 24, 2025

I see...
LGTM now

@xxuejie
Copy link
Collaborator Author

xxuejie commented Mar 24, 2025

Unnecessary PhantomData fields have been removed.

@xxuejie
Copy link
Collaborator Author

xxuejie commented Mar 24, 2025

Just to provide a little bit more on the background of this PR, in case anyone comes back from the future:

Some of the initial design goals are as follows:

  • DefaultMachineRunner should the be top trait everyone should interact with. When using CKB-VM, one only needs to pick an impl of DefaultMachineRunner and start from there, the choices for other types should be derived, not chosen.
  • Both the associated type design we use or, or trait generics(i.e., DefaultMachineRunner<Inner: SupportMachine, Decoder: InstDecoder>) should work for us, we chose associated type design, since one can write <xxx as DefaultMachineRunner>::Inner to fetch the inner type, while in the other case, one should know what the inner concrete type actually is.
  • Instruction decoding is one of the majority bottleneck in current CKB-VM. A lower-hanging fruit at CKB side, is that we can cache decoded instructions across multiple runs of the same CKB script. As a result, InstDecoder is abstracted, which becomes a type parameter of DefaultMachineRunner.
  • The assembly VM is designed with traces in mind. It must function together with a TraceDecoder. AsmMachine do not work together with a simple InstDecoder.
    • What we really need, is C++'s template specialization, where an AsmMachine could use a specialization of the InstDecoder trait. Unfortunately, this is not yet in stable Rust as of now(Mar 2025), maybe we will have trait specializations in Rust one day, and we can revisit the types again.
  • We should have the user specify as few types as possible.

Really, the above are the original design goals.

The introduction of Abstract*** types, and the additional AsmDefaultMachineBuilder, is really the result of having users specify as few types as possible, while navigating with the above design goals.

When I was using the original DefaultMachineBuilder, I kept hitting one of the following issues:

  • The same DefaultMachineBuilder cannot work on both the TraceMachine and the AsmMachine, the extra trait restriction of AsmMachine forbades it.
  • A user have to type a huge amount of characters to include the full types of DefaultMachineBuilder, even in the default case. I do mean it: the full type can be really long!

That's really the birth of AbstractDefaultMachineBuilder, RustDefaultMachineBuilder and AsmDefaultMachineBuilder.

While I feel this PR is long enough, a potential improvement could be added in the future: as of right now, one still needs initialization for 3 types:

  • A chosen type implementing DefaultMachineRunner trait
  • One of the 3 builder types
  • DefaultMachineRunner::Inner type

Even though when someone chooses the type implementing DefaultMachineRunner trait, the other 2 will mostly be self-evident. One still needs to manually inistantiate all 3 types and know about them, one such initialization process can be found here, and we don't expect other usages of the same API to differ from the provided example too much. Maybe in the future, we should add a builder API on DefaultMachineRunner trait itself, we hide the other 2 types behind the builder pattern. Maybe that will bring a more pleasant API to work with.

@xxuejie xxuejie merged commit cfb1d01 into nervosnetwork:develop Mar 24, 2025
16 checks passed
@xxuejie xxuejie deleted the new-initialization-trait-develop branch March 24, 2025 08:21
@mohanson mohanson mentioned this pull request Apr 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants