Skip to content
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

refactor: reinstate module cache #106

Merged
merged 24 commits into from
Jan 16, 2024
Merged

Conversation

jost-s
Copy link
Contributor

@jost-s jost-s commented Jan 5, 2024

  1. Instance cache in host crate has been removed in favor of a deserialized module cache DeserializedModuleCache. An abstraction for caching (serialized & deserialized modules) called ModuleCache was added.

  2. All logic related to modules and wasmer caching from holochain has been moved to the host crate. Consequently functions for wasmer development under iOS need to be imported from here.

Comment on lines +132 to +185
#[cfg(not(test))]
/// one hundred giga ops
pub const WASM_METERING_LIMIT: u64 = 100_000_000_000;

#[cfg(test)]
/// ten mega ops.
/// We don't want tests to run forever, and it can take several minutes for 100 giga ops to run.
pub const WASM_METERING_LIMIT: u64 = 10_000_000;

/// Generate an engine with a wasm compiler
/// and Metering (use limits) in place.
pub fn make_compiler_engine() -> Engine {
let cost_function = |_operator: &wasmparser::Operator| -> u64 { 1 };
// @todo 100 giga-ops is totally arbitrary cutoff so we probably
// want to make the limit configurable somehow.
let metering = Arc::new(Metering::new(WASM_METERING_LIMIT, cost_function));
// the only place where the wasm compiler engine is set
let mut compiler = Cranelift::default();
compiler.canonicalize_nans(true).push_middleware(metering);
Engine::from(compiler)
}

/// Generate a runtime `Engine` without compiler suitable for iOS.
/// Useful for re-building an iOS Module from a preserialized WASM Module.
pub fn make_ios_runtime_engine() -> Engine {
Engine::headless()
}

/// Take WASM binary and prepare a wasmer Module suitable for iOS
pub fn build_ios_module(wasm: &[u8]) -> Result<Module, CompileError> {
info!(
"Found wasm and was instructed to serialize it for ios in wasmer format, doing so now..."
);
let compiler_engine = make_compiler_engine();
let store = Store::new(compiler_engine);
Module::from_binary(&store, wasm)
}

/// Deserialize a previously compiled module for iOS from a file.
pub fn get_ios_module_from_file(path: &PathBuf) -> Result<Module, DeserializeError> {
let engine = make_ios_runtime_engine();
unsafe { Module::deserialize_from_file(&engine, path) }
}

/// Configuration of a Target for wasmer for iOS
pub fn wasmer_ios_target() -> Target {
// use what I see in
// platform ios headless example
// https://github.com/wasmerio/wasmer/blob/447c2e3a152438db67be9ef649327fabcad6f5b8/examples/platform_ios_headless.rs#L38-L53
let triple = Triple::from_str("aarch64-apple-ios").unwrap();
let cpu_feature = CpuFeature::set();
Target::new(triple, cpu_feature)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

All these items come from holochain_types::wasmer_types

@@ -128,7 +192,12 @@ pub struct SerializedModuleCache {
pub plru: MicroCache,
pub key_map: PlruKeyMap,
pub cache: BTreeMap<CacheKey, Arc<SerializedModule>>,
pub cranelift: fn() -> Cranelift,
// a function to create a new compiler engine for every module
pub make_compiler_engine: fn() -> Engine,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

To keep this independent of the compiler, I changed it return an engine

pub fn default_with_cranelift(cranelift: fn() -> Cranelift) -> Self {
/// Build a default `SerializedModuleCache` with a fn to create an `Engine`
/// that will be used to compile modules from wasms as needed.
pub fn default_with_engine(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

arguably this can be renamed to new_with_engine or just new

Copy link
Member

Choose a reason for hiding this comment

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

Sounds reasonable to me, is this a breaking change anyway? If so I'd go for the rename. Otherwise maybe leave a comment behind to do it later?

Comment on lines +279 to +280
// wasm_module,
// wasm_instance,
Copy link
Contributor Author

@jost-s jost-s Jan 10, 2024

Choose a reason for hiding this comment

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

currently the bench fails because such numerous deserialization of modules causes memory leaks because of an upstream issue where the memory for deserialization is kept as long as the engine lives wasmerio/wasmer#4377 (comment)

This shouldn't affect Holochain in practice because we're only deserializing every module once.

Copy link
Member

Choose a reason for hiding this comment

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

Can a note be left in the code here so we don't have to trace back to this PR to figure out why these are commented out later?

@jost-s jost-s marked this pull request as ready for review January 10, 2024 21:35
Copy link
Member

@ThetaSinner ThetaSinner left a comment

Choose a reason for hiding this comment

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

Looks good to me, have left a few comments but nothing that really concerns me so I'll approve and leave them with you :)

@@ -1,3 +1,5 @@
[workspace]
members = ["crates/host", "crates/common"]
exclude = ["test"]

resolver = "2"
Copy link
Member

Choose a reason for hiding this comment

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

Thanks! Makes debugging feature problems so much easier


/// Generate a runtime `Engine` without compiler suitable for iOS.
/// Useful for re-building an iOS Module from a preserialized WASM Module.
pub fn make_ios_runtime_engine() -> Engine {
Copy link
Member

Choose a reason for hiding this comment

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

Should these be behind a platform flag so they aren't available everywhere? Or are they valid anywhere but just recommended for iOS?

Actually I think even in the second case, if they have ios in the name I'd be in favour of them being behind a platform condition

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They haven't been behind a feature flag before inside holochain, I just ported these over.

Copy link
Member

Choose a reason for hiding this comment

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

Understood, I still think they probably ought to be but as this was just moved code it can wait for a less busy time :)

Copy link
Contributor Author

@jost-s jost-s Jan 11, 2024

Choose a reason for hiding this comment

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

Another limiting factor is that where they're used in holochain is in an if..else assignment, so I think we can't hide a part of it behind a feature. https://github.com/holochain/holochain/pull/3152/files#diff-14f820cc8b4334c432a642d67c2e4de0fb36249b4dd73b8bf6124482abc45427R350-R356

pub fn default_with_cranelift(cranelift: fn() -> Cranelift) -> Self {
/// Build a default `SerializedModuleCache` with a fn to create an `Engine`
/// that will be used to compile modules from wasms as needed.
pub fn default_with_engine(
Copy link
Member

Choose a reason for hiding this comment

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

Sounds reasonable to me, is this a breaking change anyway? If so I'd go for the rename. Otherwise maybe leave a comment behind to do it later?

(deserialized_module, serialized_module)
}
// no serialized module found on the file system, so serialize the
// wasm binary and write it to the file system
_fs_miss => {
Copy link
Member

Choose a reason for hiding this comment

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

Is a file system miss the only option here? What about other errors in a Some(Err(_))?

Copy link
Member

Choose a reason for hiding this comment

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

I know this is existing code, just checking while we're here and looking at it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In case of a Some(Err) the err will be swallowed, something to improve I agree

}

#[test]
fn cache_test() {
Copy link
Member

Choose a reason for hiding this comment

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

Should these tests be in a module that is only available in test config?

Copy link
Member

Choose a reason for hiding this comment

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

Also, YAY tests! :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wonder too

Copy link
Member

Choose a reason for hiding this comment

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

Ahh okay, again this is an existing pattern for this create. Understood, something for another time!

Copy link
Contributor

Choose a reason for hiding this comment

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

@ThetaSinner @jost-s uh no? there are tests in the test folder, why is this here? 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@thedavidmeister To have access to the member fields. If I put the test in the tests folder, I need to make the member fields public.

Copy link
Contributor

Choose a reason for hiding this comment

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

@jost-s ah, so then can we put it behind a cfg?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Comment on lines +279 to +280
// wasm_module,
// wasm_instance,
Copy link
Member

Choose a reason for hiding this comment

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

Can a note be left in the code here so we don't have to trace back to this PR to figure out why these are commented out later?

Copy link
Contributor

@thedavidmeister thedavidmeister left a comment

Choose a reason for hiding this comment

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

only thing is i'm not sure why there are tests in the main crates rather than test crates

}

#[test]
fn cache_test() {
Copy link
Contributor

Choose a reason for hiding this comment

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

@ThetaSinner @jost-s uh no? there are tests in the test folder, why is this here? 😅

@jost-s jost-s merged commit c22964a into develop Jan 16, 2024
5 checks passed
@jost-s jost-s deleted the refactor/reinstate-module-cache branch January 16, 2024 17:00
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