diff --git a/Cargo.lock b/Cargo.lock index e743591..996e337 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -57,7 +57,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -67,14 +67,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.59.0", + "windows-sys", ] [[package]] name = "anyhow" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" [[package]] name = "autocfg" @@ -133,6 +133,16 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "bstr" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -147,9 +157,9 @@ checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" [[package]] name = "cc" -version = "1.2.1" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" +checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf" dependencies = [ "jobserver", "libc", @@ -184,9 +194,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.22" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69371e34337c4c984bbe322360c2547210bf632eb2814bbe78a6e87a2935bd2b" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" dependencies = [ "clap_builder", "clap_derive", @@ -194,9 +204,9 @@ dependencies = [ [[package]] name = "clap-verbosity-flag" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54381ae56ad222eea3f529c692879e9c65e07945ae48d3dc4d1cb18dbec8cf44" +checksum = "2678fade3b77aa3a8ff3aae87e9c008d3fb00473a41c71fbf74e91c8c7b37e84" dependencies = [ "clap", "tracing-core", @@ -204,9 +214,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.22" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e24c1b4099818523236a8ca881d2b45db98dadfb4625cf6608c12069fcbbde1" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" dependencies = [ "anstream", "anstyle", @@ -217,9 +227,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.38" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9647a559c112175f17cf724dc72d3645680a883c58481332779192b0d8e7a01" +checksum = "ac2e663e3e3bed2d32d065a8404024dad306e699a04263ec59919529f803aee9" dependencies = [ "clap", ] @@ -238,17 +248,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" - -[[package]] -name = "cmake" -version = "0.1.50" -source = "git+https://github.com/blonteractor/cmake-rs#b96aab10486f2c1540422dd4e6f38e983745fd5a" -dependencies = [ - "cc", -] +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "colorchoice" @@ -264,31 +266,47 @@ checksum = "baf0a07a401f374238ab8e2f11a104d2851bf9ce711ec69804834de8af45c7af" [[package]] name = "console" -version = "0.15.8" +version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" dependencies = [ "encode_unicode", - "lazy_static", "libc", - "unicode-width 0.1.14", - "windows-sys 0.52.0", + "once_cell", + "unicode-width 0.2.0", + "windows-sys", ] [[package]] -name = "diffy" -version = "0.4.0" +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d3041965b7a63e70447ec818a46b1e5297f7fcae3058356d226c02750c4e6cb" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "nu-ansi-term 0.50.1", + "crossbeam-utils", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "divan" -version = "0.1.15" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e05d17bd4ff1c1e7998ed4623d2efd91f72f1e24141ac33aac9377974270e1f" +checksum = "e0583193020b29b03682d8d33bb53a5b0f50df6daacece12ca99b904cfdcb8c4" dependencies = [ "cfg-if", "clap", @@ -300,9 +318,9 @@ dependencies = [ [[package]] name = "divan-macros" -version = "0.1.15" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b4464d46ce68bfc7cb76389248c7c254def7baca8bece0693b02b83842c4c88" +checksum = "8dc51d98e636f5e3b0759a39257458b22619cac7e96d932da6eeb052891bb67c" dependencies = [ "proc-macro2", "quote", @@ -323,18 +341,18 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "encode_unicode" -version = "0.3.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -350,9 +368,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "flume" @@ -389,12 +407,41 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "globset" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "ignore" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata 0.4.9", + "same-file", + "walkdir", + "winapi-util", +] + [[package]] name = "indicatif" version = "0.17.9" @@ -440,10 +487,11 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -455,15 +503,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.164" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "libloading" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", "windows-targets", @@ -524,7 +572,7 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mnn" -version = "0.2.0" +version = "0.3.0" dependencies = [ "anyhow", "bytemuck", @@ -532,9 +580,7 @@ dependencies = [ "divan", "dunce", "error-stack", - "libc", "mnn-sys", - "oneshot", "serde", "thiserror", "tracing", @@ -566,19 +612,17 @@ dependencies = [ [[package]] name = "mnn-sys" -version = "0.1.0" +version = "0.2.0" dependencies = [ - "anyhow", "bindgen", "cc", - "cmake", - "diffy", - "dunce", + "error-stack", "fs_extra", - "itertools", - "libc", + "ignore", "once_cell", + "pkg-config", "tap", + "thiserror", "tracing-core", ] @@ -639,15 +683,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "nu-ansi-term" -version = "0.50.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" -dependencies = [ - "windows-sys 0.52.0", -] - [[package]] name = "num" version = "0.4.3" @@ -751,17 +786,23 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + [[package]] name = "portable-atomic" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" [[package]] name = "portable-atomic-util" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90a7d5beecc52a491b54d6dd05c7a45ba1801666a5baad9fdbfc6fef8d2d206c" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" dependencies = [ "portable-atomic", ] @@ -778,9 +819,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -867,15 +908,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.41" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -901,24 +942,24 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "semver" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" [[package]] name = "serde" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" dependencies = [ "proc-macro2", "quote", @@ -975,9 +1016,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.87" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -1000,33 +1041,33 @@ dependencies = [ "fastrand", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys", ] [[package]] name = "terminal_size" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" +checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" dependencies = [ "rustix", - "windows-sys 0.59.0", + "windows-sys", ] [[package]] name = "thiserror" -version = "2.0.4" +version = "2.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f49a1853cf82743e3b7950f77e0f4d622ca36cf4317cba00c767838bac8d490" +checksum = "08f5383f3e0071702bf93ab5ee99b52d26936be9dedd9413067cbdcddcb6141a" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.4" +version = "2.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8381894bb3efe0c4acac3ded651301ceee58a15d47c2e34885ed1908ad667061" +checksum = "f2f357fcec90b3caef6623a099691be676d033b40a058ac95d2a6ade6fa0c943" dependencies = [ "proc-macro2", "quote", @@ -1093,7 +1134,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", - "nu-ansi-term 0.46.0", + "nu-ansi-term", "once_cell", "regex", "sharded-slab", @@ -1127,9 +1168,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-width" @@ -1155,6 +1196,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1163,9 +1214,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", "once_cell", @@ -1174,13 +1225,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn", @@ -1189,9 +1239,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1199,9 +1249,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", @@ -1212,9 +1262,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "web-time" @@ -1248,7 +1298,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -1257,15 +1307,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-sys" version = "0.59.0" diff --git a/Cargo.toml b/Cargo.toml index f8db97d..1ffdae5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,42 +2,49 @@ members = [".", "mnn-bridge", "mnn-sync", "mnn-sys", "tools/bencher"] [workspace.package] license = "Apache-2.0" +repository = "https://github.com/aftershootco/mnn-rs" [package] name = "mnn" -version = "0.2.0" +version = "0.3.0" edition = "2021" license = { workspace = true } +repository = { workspace = true } +documentation = "https://docs.rs/mnn" +description = "High level MNN to Rust bindings" [workspace.dependencies] -mnn = { version = "0.2.0", path = "." } +mnn = { version = "0.3.0", path = "." } error-stack = { version = "0.5" } [dependencies] -libc = "0.2" -mnn-sys = { version = "0.1", path = "mnn-sys", features = [] } -thiserror = "2.0" +mnn-sys = { version = "0.2", path = "mnn-sys", default-features = false, features = [ + "mnn-threadpool", + "sparse-compute", +] } +thiserror = "2" error-stack.workspace = true -oneshot = "0.1" tracing = { version = "0.1.40", optional = true } dunce = "1.0.5" serde = { version = "1.0", features = ["derive"], optional = true } [features] +opencl = ["mnn-sys/opencl"] + metal = ["mnn-sys/metal"] coreml = ["mnn-sys/coreml"] -vulkan = ["mnn-sys/vulkan"] -opencl = ["mnn-sys/opencl"] -opengl = ["mnn-sys/opengl"] + +vulkan = [] # This is currently unimplemented + crt_static = ["mnn-sys/crt_static"] -# Disable mnn-threadpool to enable this -openmp = ["mnn-sys/openmp"] -mnn-threadpool = ["mnn-sys/mnn-threadpool"] + tracing = ["dep:tracing"] profile = ["tracing"] serde = ["dep:serde"] -default = ["mnn-threadpool"] +simd = ["mnn-sys/simd"] + +default = ["simd"] [dev-dependencies] diff --git a/benches/mnn-bench.rs b/benches/mnn-bench.rs index 3c59838..f05e438 100644 --- a/benches/mnn-bench.rs +++ b/benches/mnn-bench.rs @@ -5,7 +5,7 @@ mod mnn_realesr_bench_with_ones { use mnn::*; #[divan::bench] pub fn mnn_realesr_benchmark_cpu(bencher: Bencher) { - let mut net = Interpreter::from_file("tests/assets/realesr.mnn").unwrap(); + let net = Interpreter::from_file("tests/assets/realesr.mnn").unwrap(); let mut config = ScheduleConfig::new(); config.set_type(ForwardType::CPU); let session = net.create_session(config).unwrap(); @@ -19,7 +19,7 @@ mod mnn_realesr_bench_with_ones { #[cfg(feature = "opencl")] #[divan::bench] pub fn mnn_realesr_benchmark_opencl(bencher: Bencher) { - let mut net = Interpreter::from_file("tests/assets/realesr.mnn").unwrap(); + let net = Interpreter::from_file("tests/assets/realesr.mnn").unwrap(); let mut config = ScheduleConfig::new(); config.set_type(ForwardType::OpenCL); let session = net.create_session(config).unwrap(); @@ -30,4 +30,19 @@ mod mnn_realesr_bench_with_ones { net.wait(&session); }); } + + #[cfg(feature = "metal")] + #[divan::bench] + pub fn mnn_realesr_benchmark_metal(bencher: Bencher) { + let net = Interpreter::from_file("tests/assets/realesr.mnn").unwrap(); + let mut config = ScheduleConfig::new(); + config.set_type(ForwardType::Metal); + let session = net.create_session(config).unwrap(); + bencher.bench_local(|| { + let mut input = net.input(&session, "data").unwrap(); + input.fill(1f32); + net.run_session(&session).unwrap(); + net.wait(&session); + }); + } } diff --git a/deny.toml b/deny.toml index 1e7ddbc..e6e6c88 100644 --- a/deny.toml +++ b/deny.toml @@ -88,7 +88,7 @@ ignore = [ # List of explicitly allowed licenses # See https://spdx.org/licenses/ for list of possible licenses # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. -allow = ["MIT", "Apache-2.0", "BSD-3-Clause", "ISC", "Unicode-DFS-2016", "Zlib"] +allow = ["MIT", "Apache-2.0", "BSD-3-Clause", "ISC", "Unicode-3.0"] # The confidence threshold for detecting a license from license text. # The higher the value, the more closely the license text must be to the # canonical license text of a valid SPDX license file. diff --git a/examples/inspect.rs b/examples/inspect.rs index eb162a9..4e1fd12 100644 --- a/examples/inspect.rs +++ b/examples/inspect.rs @@ -47,9 +47,9 @@ macro_rules! time { pub fn main() -> anyhow::Result<()> { use clap::Parser; let cli = Cli::parse(); - let mut interpreter = Interpreter::from_file(&cli.model)?; + let net = Interpreter::from_file(&cli.model)?; if !cli.no_cache { - interpreter.set_cache_file(cli.model.with_extension("cache"), 128)?; + net.set_cache_file(cli.model.with_extension("cache"), 128)?; } tracing_subscriber::fmt() @@ -62,22 +62,22 @@ pub fn main() -> anyhow::Result<()> { let mut config = ScheduleConfig::new(); config.set_type(cli.forward); - let mut session = time!(interpreter.create_session(config)?; "create session"); + let mut session = time!(net.create_session(config)?; "create session"); if !cli.no_cache { - interpreter.update_cache_file(&mut session)?; + net.update_cache_file(&mut session)?; } let mut current = 0; println!("--------------------------------Info--------------------------------"); - let mem = interpreter.memory(&session)?; - let flops = interpreter.flops(&session)?; + let mem = net.memory(&session)?; + let flops = net.flops(&session)?; println!("Memory: {:?}MiB", mem); println!("Flops : {:?}M", flops); - println!("ResizeStatus : {:?}", interpreter.resize_status(&session)?); + println!("ResizeStatus : {:?}", net.resize_status(&session)?); time!(loop { println!("--------------------------------Inputs--------------------------------"); - interpreter.inputs(&session).iter().for_each(|x| { + net.inputs(&session).iter().for_each(|x| { match cli.input_data_type { DataType::F32 => { let mut tensor = x.tensor::().expect("No tensor"); @@ -98,9 +98,9 @@ pub fn main() -> anyhow::Result<()> { }); println!("Running session"); - interpreter.run_session(&session)?; + net.run_session(&session)?; println!("--------------------------------Outputs--------------------------------"); - let outputs = interpreter.outputs(&session); + let outputs = net.outputs(&session); outputs.iter().for_each(|x| { match cli.output_data_type { DataType::F32 => { diff --git a/examples/simple.rs b/examples/simple.rs index 11952b1..b572c4f 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -17,7 +17,7 @@ pub struct Cli { pub fn main() -> anyhow::Result<()> { use clap::Parser; let cli = Cli::parse(); - let mut interpreter = Interpreter::from_file(cli.model)?; + let net = Interpreter::from_file(cli.model)?; let mut config = ScheduleConfig::new(); config.set_type(ForwardType::CPU); @@ -27,10 +27,10 @@ pub fn main() -> anyhow::Result<()> { config.set_backend_config(backend_config); let now = std::time::Instant::now(); - let session = interpreter.create_session(config)?; + let session = net.create_session(config)?; println!("create session time: {:?}", now.elapsed()); - let mut image = interpreter.input(&session, "image")?; - let mut mask = interpreter.input(&session, "mask")?; + let mut image = net.input(&session, "image")?; + let mut mask = net.input(&session, "mask")?; let mut image_tensor = image.create_host_tensor_from_device(false); image_tensor.host_mut().fill(1.0f32); image.copy_from_host_tensor(&image_tensor)?; @@ -40,11 +40,11 @@ pub fn main() -> anyhow::Result<()> { mask.copy_from_host_tensor(&mask_tensor)?; println!("copy time: {:?}", now.elapsed()); - let output = interpreter.output(&session, "output")?; + let output = net.output(&session, "output")?; // image.copy_from_host_tensor(&unit_tensor)?; let now = std::time::Instant::now(); - interpreter.run_session(&session)?; + net.run_session(&session)?; output.wait(ffi::MapType::MAP_TENSOR_READ, true); println!("run time: {:?}", now.elapsed()); diff --git a/flake.lock b/flake.lock index 82135aa..77b4d5c 100644 --- a/flake.lock +++ b/flake.lock @@ -145,11 +145,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1733940404, - "narHash": "sha256-Pj39hSoUA86ZePPF/UXiYHHM7hMIkios8TYG29kQT4g=", + "lastModified": 1734119587, + "narHash": "sha256-AKU6qqskl0yf2+JdRdD0cfxX4b9x3KKV5RqA6wijmPM=", "owner": "nixos", "repo": "nixpkgs", - "rev": "5d67ea6b4b63378b9c13be21e2ec9d1afc921713", + "rev": "3566ab7246670a43abd2ffa913cc62dad9cdf7d5", "type": "github" }, "original": { @@ -178,11 +178,11 @@ ] }, "locked": { - "lastModified": 1734057252, - "narHash": "sha256-fpSFuiW+O2L0ru2GrXBS0wcAYV9+yDE0Gf800UsWutY=", + "lastModified": 1734402816, + "narHash": "sha256-cgQ8mjUJz7J3fp97lnvl0dSJ6vLt8yzUSmw3B7QKw94=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "1f56a5c88e4dcaa0ab1ba04c4bc5a977cff840b2", + "rev": "e38fbd6e56e8cd1d61c65a21bbb7785e966707b4", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 6ed5717..d007e57 100644 --- a/flake.nix +++ b/flake.nix @@ -111,7 +111,7 @@ mnn-docs = craneLib.cargoDoc (commonArgs // { inherit cargoArtifacts; - cargoDocExtraArgs = "-p mnn -p mnn-sys"; + cargoDocExtraArgs = "-p mnn -p mnn-sys -p mnn-bridge -p mnn-sync"; }); mnn-fmt = craneLib.cargoFmt {inherit src;}; # Audit dependencies @@ -226,6 +226,7 @@ cargo-llvm-cov ] ); + # ++ (with packages; [bencher inspect]); }); }; } diff --git a/mnn-sync/src/lib.rs b/mnn-sync/src/lib.rs index 75b1ab5..177c872 100644 --- a/mnn-sync/src/lib.rs +++ b/mnn-sync/src/lib.rs @@ -128,7 +128,7 @@ impl SessionRunnerState { tracing::trace!("Unloading session"); match core::mem::take(self) { Self::Loaded(sr) => { - let net = sr.unload()?; + let net = sr.unload(); *self = Self::Unloaded(net); Ok(()) } @@ -200,23 +200,25 @@ impl SessionState { #[derive(Debug)] pub struct SessionRunner { pub interpreter: Interpreter, - pub session: Session, + pub session: Session<'static>, } -impl SessionRunner { - pub fn new(interpreter: Interpreter, session: Session) -> Self { - Self { - interpreter, - session, - } - } +// impl Drop for SessionRunner { +// fn drop(&mut self) { +// drop(self.session); +// drop(self.interpreter); +// } +// } - pub fn create(mut net: Interpreter, config: ScheduleConfig) -> Result { +impl SessionRunner { + pub fn create(net: Interpreter, config: ScheduleConfig) -> Result { #[cfg(feature = "tracing")] tracing::trace!("Creating session"); #[cfg(feature = "tracing")] let now = std::time::Instant::now(); - let mut session = net.create_session(config)?; + let mut session = unsafe { + core::mem::transmute::, Session<'static>>(net.create_session(config)?) + }; net.update_cache_file(&mut session)?; #[cfg(feature = "tracing")] tracing::trace!("Session created in {:?}", now.elapsed()); @@ -226,20 +228,20 @@ impl SessionRunner { }) } - pub fn unload(self) -> Result { + pub fn unload(self) -> mnn::Interpreter { let session = self.session; let net = self.interpreter; drop(session); - Ok(net) + net } pub fn run_session(&mut self) -> Result<()> { self.interpreter.run_session(&self.session) } - pub fn both_mut(&mut self) -> (&mut Interpreter, &mut Session) { - (&mut self.interpreter, &mut self.session) - } + // pub fn both_mut(&mut self) -> (&mut Interpreter, &mut Session) { + // (&mut self.interpreter, &mut self.session) + // } pub fn resize_session(&mut self) -> Result<()> { self.interpreter.resize_session(&mut self.session); @@ -258,7 +260,7 @@ impl SessionRunner { &self.session } - pub fn session_mut(&mut self) -> &mut Session { + pub fn session_mut(&mut self) -> &mut Session<'static> { &mut self.session } @@ -504,7 +506,6 @@ pub fn test_sync_api() { } #[test] -#[ignore = "This test is not reliable on CI"] pub fn test_sync_api_race() { let interpreter = Interpreter::from_file("../tests/assets/realesr.mnn") .expect("Failed to create interpreter"); diff --git a/mnn-sys/Cargo.toml b/mnn-sys/Cargo.toml index 8a3befb..08075d1 100644 --- a/mnn-sys/Cargo.toml +++ b/mnn-sys/Cargo.toml @@ -1,35 +1,57 @@ [package] name = "mnn-sys" -version = "0.1.0" +version = "0.2.0" edition = "2021" links = "mnn" license = { workspace = true } +repository = { workspace = true } +description = "Low-level bindings to MNN" +include = [ + "/src", + "/vendor/source", + "/vendor/3rd_party/flatbuffers", + "/vendor/3rd_party/half", + "/vendor/3rd_party/OpenCLHeaders", + "/vendor/include", + "/vendor/schema", + "/build.rs", +] [build-dependencies] -anyhow = "1.0.86" bindgen = { version = "0.70", features = ["experimental"] } -cc = { version = "1.1.5", features = [] } -cmake = { git = "https://github.com/blonteractor/cmake-rs", features = [ - "parallel", -] } -diffy = "0.4.0" -dunce = "1.0.4" +cc = { version = "1.1.5", features = ["parallel"] } +error-stack.workspace = true fs_extra = "1.3.0" -itertools = "0.13.0" +ignore = "0.4.23" +pkg-config = "0.3.31" tap = "1.0.1" +thiserror = "2.0.3" [features] -vulkan = [] +opencl = [] + metal = [] coreml = ["metal"] -opencl = [] -openmp = [] -opengl = [] +vulkan = [] + mnn-threadpool = [] -default = ["mnn-threadpool"] + crt_static = [] +mini-build = [] +sparse-compute = [] +arm82 = [] +bf16 = [] +cpu-weight-dequant-gemm = [] + +# Disable if you don't plan to use cpu backend and want quicker compilation +sse = [] +avx512 = [] +neon = [] +simd = ["sse", "avx512", "neon"] + +low-memory = [] +default = ["mnn-threadpool", "sparse-compute", "opencl", "simd"] [dependencies] -libc = "0.2.155" once_cell = "1.20.2" tracing-core = "0.1.33" diff --git a/mnn-sys/build.rs b/mnn-sys/build.rs index 636dc01..5d06bea 100644 --- a/mnn-sys/build.rs +++ b/mnn-sys/build.rs @@ -1,18 +1,46 @@ use ::tap::*; -use anyhow::*; +use error_stack::*; +#[derive(Debug, thiserror::Error)] +#[error("Failed to build mnn-sys")] +pub struct Error; +pub type Result> = core::result::Result; #[cfg(unix)] use std::os::unix::fs::PermissionsExt; use std::{ + ffi::OsStr, path::{Path, PathBuf}, sync::LazyLock, }; -const VENDOR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/vendor"); + +static VENDOR: LazyLock = LazyLock::new(|| { + std::env::var("MNN_SRC") + .map(PathBuf::from) + .ok() + .unwrap_or_else(|| PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/vendor"))) +}); const MANIFEST_DIR: &str = env!("CARGO_MANIFEST_DIR"); +static TARGET_POINTER_WIDTH: LazyLock = LazyLock::new(|| { + std::env::var("CARGO_CFG_TARGET_POINTER_WIDTH") + .expect("CARGO_CFG_TARGET_POINTER_WIDTH not set") + .parse() + .expect("Failed to parse CARGO_CFG_TARGET_POINTER_WIDTH") +}); + +static TARGET_FEATURES: LazyLock> = LazyLock::new(|| { + std::env::var("CARGO_CFG_TARGET_FEATURE") + .expect("CARGO_CFG_TARGET_FEATURE not set") + .split(',') + .map(|s| s.to_string()) + .collect() +}); + static TARGET_OS: LazyLock = LazyLock::new(|| std::env::var("CARGO_CFG_TARGET_OS").expect("CARGO_CFG_TARGET_OS not set")); + static TARGET_ARCH: LazyLock = LazyLock::new(|| { std::env::var("CARGO_CFG_TARGET_ARCH").expect("CARGO_CFG_TARGET_ARCH not found") }); + static EMSCRIPTEN_CACHE: LazyLock = LazyLock::new(|| { let emscripten_cache = std::process::Command::new("em-config") .arg("CACHE") @@ -64,34 +92,55 @@ void mnn_ffi_emit(const char *file, size_t line, Level level, } "#; +const STATIC_CRT: bool = cfg!(feature = "crt_static"); + +fn has_cpu_feature(feature: impl AsRef) -> bool { + let feature = feature.as_ref(); + TARGET_FEATURES.iter().any(|f| *f == feature) +} + fn ensure_vendor_exists(vendor: impl AsRef) -> Result<()> { if vendor .as_ref() .read_dir() - .with_context(|| format!("Vendor directory missing: {}", vendor.as_ref().display()))? + .change_context(Error) + .attach_printable_lazy(|| { + format!("Vendor directory missing: {}", vendor.as_ref().display()) + })? .flatten() .count() == 0 { - anyhow::bail!("Vendor not found maybe you need to run \"git submodule update --init\"") + return Err(Report::new(Error).attach_printable( + "Vendor not found maybe you need to run \"git submodule update --init\"", + )); } Ok(()) } -fn main() -> Result<()> { +fn main() { + match _main() { + Ok(_) => (), + Err(e) => { + Report::set_color_mode(fmt::ColorMode::Color); + Report::set_charset(fmt::Charset::default()); + eprintln!("{e:?}"); + panic!("Failed to compile mnn-sys") + } + } +} + +fn _main() -> Result<()> { + #[cfg(any(feature = "vulkan", feature = "coreml"))] + compile_error!("Vulkan, CoreML are not supported currently"); println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-env-changed=MNN_SRC"); - let out_dir = PathBuf::from(std::env::var("OUT_DIR")?); - let source = PathBuf::from( - std::env::var("MNN_SRC") - .ok() - .unwrap_or_else(|| VENDOR.into()), - ); + let out_dir = PathBuf::from(std::env::var("OUT_DIR").change_context(Error)?); + let source = (*VENDOR).clone(); ensure_vendor_exists(&source)?; let vendor = out_dir.join("vendor"); - // std::fs::remove_dir_all(&vendor).ok(); if !vendor.exists() { fs_extra::dir::copy( &source, @@ -100,18 +149,16 @@ fn main() -> Result<()> { .overwrite(true) .copy_inside(true), ) - .context("Failed to copy vendor")?; - let intptr = vendor.join("include").join("MNN").join("HalideRuntime.h"); + .change_context(Error) + .attach_printable("Failed to copy vendor")?; + let halide_runtime_h = vendor.join("include").join("MNN").join("HalideRuntime.h"); #[cfg(unix)] - std::fs::set_permissions(&intptr, std::fs::Permissions::from_mode(0o644))?; + std::fs::set_permissions(&halide_runtime_h, std::fs::Permissions::from_mode(0o644)) + .change_context(Error)?; - use itertools::Itertools; - let intptr_contents = std::fs::read_to_string(&intptr)?; + let intptr_contents = std::fs::read_to_string(&halide_runtime_h).change_context(Error)?; let patched = intptr_contents.lines().collect::>(); - if let Some((idx, _)) = patched - .iter() - .find_position(|line| line.contains(HALIDE_SEARCH)) - { + if let Some(idx) = patched.iter().position(|line| line.contains(HALIDE_SEARCH)) { // remove the last line and the next 3 lines let patched = patched .into_iter() @@ -120,34 +167,32 @@ fn main() -> Result<()> { .map(|(_, c)| c) .collect::>(); - std::fs::write(intptr, patched.join("\n"))?; + std::fs::write(halide_runtime_h, patched.join("\n")).change_context(Error)?; } let mnn_define = vendor.join("include").join("MNN").join("MNNDefine.h"); - let patched = - std::fs::read_to_string(&mnn_define)?.replace(TRACING_SEARCH, TRACING_REPLACE); + let patched = std::fs::read_to_string(&mnn_define) + .change_context(Error)? + .replace(TRACING_SEARCH, TRACING_REPLACE); #[cfg(unix)] - std::fs::set_permissions(&mnn_define, std::fs::Permissions::from_mode(0o644))?; - std::fs::write(mnn_define, patched)?; + std::fs::set_permissions(&mnn_define, std::fs::Permissions::from_mode(0o644)) + .change_context(Error)?; + std::fs::write(mnn_define, patched).change_context(Error)?; } if *MNN_COMPILE { - let install_dir = out_dir.join("mnn-install"); - build_cmake(&vendor, &install_dir)?; - println!( - "cargo:rustc-link-search=native={}", - install_dir.join("lib").display() - ); + mnn_cpp_build(&vendor)?; } else if let core::result::Result::Ok(lib_dir) = std::env::var("MNN_LIB_DIR") { println!("cargo:rustc-link-search=native={}", lib_dir); + println!("cargo:rustc-link-lib=static=MNN"); } else { panic!("MNN_LIB_DIR not set while MNN_COMPILE is false"); } mnn_c_build(PathBuf::from(MANIFEST_DIR).join("mnn_c"), &vendor) - .with_context(|| "Failed to build mnn_c")?; - mnn_c_bindgen(&vendor, &out_dir).with_context(|| "Failed to generate mnn_c bindings")?; - mnn_cpp_bindgen(&vendor, &out_dir).with_context(|| "Failed to generate mnn_cpp bindings")?; + .attach_printable("Failed to build mnn_c")?; + mnn_c_bindgen(&vendor, &out_dir).attach_printable("Failed to generate mnn_c bindings")?; + mnn_cpp_bindgen(&vendor, &out_dir).attach_printable("Failed to generate mnn_cpp bindings")?; println!("cargo:include={vendor}/include", vendor = vendor.display()); if *TARGET_OS == "macos" { #[cfg(feature = "metal")] @@ -162,19 +207,23 @@ fn main() -> Result<()> { println!("cargo:rustc-link-lib=framework=CoreVideo"); #[cfg(feature = "opencl")] println!("cargo:rustc-link-lib=framework=OpenCL"); - #[cfg(feature = "opengl")] - println!("cargo:rustc-link-lib=framework=OpenGL"); - } else { - // #[cfg(feature = "opencl")] - // println!("cargo:rustc-link-lib=static=opencl"); + } else if *TARGET_OS == "linux" { + #[cfg(feature = "opencl")] + { + if pkg_config::probe_library("OpenCL").is_err() { + println!("cargo:rustc-link-lib=static=OpenCL"); + }; + } } if is_emscripten() { - // println!("cargo:rustc-link-lib=static=stdc++"); let emscripten_cache = std::process::Command::new("em-config") .arg("CACHE") - .output()? + .output() + .change_context(Error)? .stdout; - let emscripten_cache = std::str::from_utf8(&emscripten_cache)?.trim(); + let emscripten_cache = std::str::from_utf8(&emscripten_cache) + .change_context(Error)? + .trim(); let wasm32_emscripten_libs = PathBuf::from(emscripten_cache).join("sysroot/lib/wasm32-emscripten"); println!( @@ -182,16 +231,19 @@ fn main() -> Result<()> { wasm32_emscripten_libs.display() ); } - println!("cargo:rustc-link-lib=static=MNN"); Ok(()) } pub fn mnn_c_bindgen(vendor: impl AsRef, out: impl AsRef) -> Result<()> { let vendor = vendor.as_ref(); let mnn_c = PathBuf::from(MANIFEST_DIR).join("mnn_c"); - mnn_c.read_dir()?.flatten().for_each(|e| { - rerun_if_changed(e.path()); - }); + mnn_c + .read_dir() + .change_context(Error)? + .flatten() + .for_each(|e| { + rerun_if_changed(e.path()); + }); const HEADERS: &[&str] = &[ "error_code_c.h", "interpreter_c.h", @@ -201,7 +253,6 @@ pub fn mnn_c_bindgen(vendor: impl AsRef, out: impl AsRef) -> Result< ]; let bindings = bindgen::Builder::default() - // .clang_args(["-x", "c++"]) .clang_arg(CxxOption::VULKAN.cxx()) .clang_arg(CxxOption::METAL.cxx()) .clang_arg(CxxOption::COREML.cxx()) @@ -246,8 +297,11 @@ pub fn mnn_c_bindgen(vendor: impl AsRef, out: impl AsRef) -> Result< // // eprintln!("Full bindgen: {}", d.command_line_flags().join(" ")); // std::fs::write("bindgen.txt", d.command_line_flags().join(" ")).ok(); // }) - .generate()?; - bindings.write_to_file(out.as_ref().join("mnn_c.rs"))?; + .generate() + .change_context(Error)?; + bindings + .write_to_file(out.as_ref().join("mnn_c.rs")) + .change_context(Error)?; Ok(()) } @@ -276,17 +330,24 @@ pub fn mnn_cpp_bindgen(vendor: impl AsRef, out: impl AsRef) -> Resul .allowlist_item(".*SessionInfoCode.*"); // let cmd = bindings.command_line_flags().join(" "); // println!("cargo:warn=bindgen: {}", cmd); - let bindings = bindings.generate()?; - bindings.write_to_file(out.as_ref().join("mnn_cpp.rs"))?; + let bindings = bindings.generate().change_context(Error)?; + bindings + .write_to_file(out.as_ref().join("mnn_cpp.rs")) + .change_context(Error)?; Ok(()) } pub fn mnn_c_build(path: impl AsRef, vendor: impl AsRef) -> Result<()> { let mnn_c = path.as_ref(); - let files = mnn_c.read_dir()?.flatten().map(|e| e.path()).filter(|e| { - e.extension() == Some(std::ffi::OsStr::new("cpp")) - || e.extension() == Some(std::ffi::OsStr::new("c")) - }); + let files = mnn_c + .read_dir() + .change_context(Error)? + .flatten() + .map(|e| e.path()) + .filter(|e| { + e.extension() == Some(std::ffi::OsStr::new("cpp")) + || e.extension() == Some(std::ffi::OsStr::new("c")) + }); let vendor = vendor.as_ref(); cc::Build::new() .include(vendor.join("include")) @@ -294,8 +355,6 @@ pub fn mnn_c_build(path: impl AsRef, vendor: impl AsRef) -> Result<( .pipe(|config| { #[cfg(feature = "vulkan")] config.define("MNN_VULKAN", "1"); - #[cfg(feature = "opengl")] - config.define("MNN_OPENGL", "1"); #[cfg(feature = "metal")] config.define("MNN_METAL", "1"); #[cfg(feature = "coreml")] @@ -308,115 +367,23 @@ pub fn mnn_c_build(path: impl AsRef, vendor: impl AsRef) -> Result<( config.target("wasm32-unknown-emscripten"); config.cpp_link_stdlib("c++-noexcept"); } - #[cfg(feature = "crt_static")] - config.static_crt(true); config }) .cpp(true) .static_flag(true) + .static_crt(STATIC_CRT) .files(files) .std("c++14") - // .pipe(|build| { - // let c = build.get_compiler(); - // use std::io::Write; - // writeln!( - // std::fs::File::create("./command.txt").unwrap(), - // "{:?}", - // c.to_command() - // ) - // .unwrap(); - // build - // }) .try_compile("mnn_c") - .context("Failed to compile mnn_c library")?; + .change_context(Error) + .attach_printable("Failed to compile mnn_c library")?; Ok(()) } -pub fn build_cmake(path: impl AsRef, install: impl AsRef) -> Result<()> { - let threads = std::thread::available_parallelism()?; - cmake::Config::new(path) - .define("CMAKE_CXX_STANDARD", "14") - .parallel(threads.get() as u8) - .define("MNN_BUILD_SHARED_LIBS", "OFF") - .define("MNN_SEP_BUILD", "OFF") - .define("MNN_PORTABLE_BUILD", "ON") - .define("MNN_USE_SYSTEM_LIB", "OFF") - .define("MNN_BUILD_CONVERTER", "OFF") - .define("MNN_BUILD_TOOLS", "OFF") - .define("CMAKE_INSTALL_PREFIX", install.as_ref()) - // https://github.com/rust-lang/rust/issues/39016 - // https://github.com/rust-lang/cc-rs/pull/717 - // .define("CMAKE_BUILD_TYPE", "Release") - .pipe(|config| { - config.define("MNN_WIN_RUNTIME_MT", CxxOption::CRT_STATIC.cmake_value()); - config.define("MNN_USE_THREAD_POOL", CxxOption::THREADPOOL.cmake_value()); - config.define("MNN_OPENMP", CxxOption::OPENMP.cmake_value()); - config.define("MNN_VULKAN", CxxOption::VULKAN.cmake_value()); - config.define("MNN_METAL", CxxOption::METAL.cmake_value()); - config.define("MNN_COREML", CxxOption::COREML.cmake_value()); - config.define("MNN_OPENCL", CxxOption::OPENCL.cmake_value()); - config.define("MNN_OPENGL", CxxOption::OPENGL.cmake_value()); - config.define("CMAKE_CXX_FLAGS", "-O0"); - // #[cfg(windows)] - if *TARGET_OS == "windows" { - config.define("CMAKE_CXX_FLAGS", "-DWIN32=1"); - } - - if is_emscripten() { - config - .define("CMAKE_C_COMPILER", "emcc") - .define("CMAKE_CXX_COMPILER", "em++") - .target("wasm32-unknown-emscripten"); - } - config - }) - .build(); - Ok(()) -} - -// pub fn try_patch_file(patch: impl AsRef, file: impl AsRef) -> Result<()> { -// let patch = dunce::canonicalize(patch)?; -// rerun_if_changed(&patch); -// let patch = std::fs::read_to_string(&patch)?; -// let patch = diffy::Patch::from_str(&patch)?; -// let file_path = file.as_ref(); -// let file = std::fs::read_to_string(file_path).context("Failed to read input file")?; -// let patched_file = -// diffy::apply(&file, &patch).context("Failed to apply patches using diffy")?; -// std::fs::write(file_path, patched_file)?; -// Ok(()) -// } - pub fn rerun_if_changed(path: impl AsRef) { println!("cargo:rerun-if-changed={}", path.as_ref().display()); } -// pub fn vulkan_includes(vendor: impl AsRef) -> Vec { -// let vendor = vendor.as_ref(); -// let vulkan_dir = vendor.join("source/backend/vulkan"); -// if cfg!(feature = "vulkan") { -// vec![ -// vulkan_dir.clone(), -// vulkan_dir.join("runtime"), -// vulkan_dir.join("component"), -// // IDK If the order is important but the cmake file does it like this -// vulkan_dir.join("buffer/execution"), -// vulkan_dir.join("buffer/backend"), -// vulkan_dir.join("buffer"), -// vulkan_dir.join("buffer/shaders"), -// // vulkan_dir.join("image/execution"), -// // vulkan_dir.join("image/backend"), -// // vulkan_dir.join("image"), -// // vulkan_dir.join("image/shaders"), -// vendor.join("schema/current"), -// vendor.join("3rd_party/flatbuffers/include"), -// vendor.join("source"), -// ] -// } else { -// vec![] -// } -// } - pub fn is_emscripten() -> bool { *TARGET_OS == "emscripten" && *TARGET_ARCH == "wasm32" } @@ -472,6 +439,19 @@ macro_rules! cxx_option_from_feature { CxxOption::from_bool($cxx, cfg!(feature = $feature)) }}; } + +macro_rules! cxx_option_from_features { + ($name:ident=> $feature: literal, $cxx:literal) => { + pub const $name: CxxOption = cxx_option_from_feature!($feature, $cxx); + }; + ($( $name:ident => $feature:literal, $cxx:literal),*) => { + $(cxx_option_from_features!($name=> $feature, $cxx);)* + + pub fn all() -> Vec { + vec![$(Self::$name),*] + } + }; +} impl CxxOption { const fn from_bool(name: &'static str, value: bool) -> Self { Self { @@ -479,15 +459,30 @@ impl CxxOption { value: CxxOptionValue::from_bool(value), } } - pub const VULKAN: CxxOption = cxx_option_from_feature!("vulkan", "MNN_VULKAN"); - pub const METAL: CxxOption = cxx_option_from_feature!("metal", "MNN_METAL"); - pub const COREML: CxxOption = cxx_option_from_feature!("coreml", "MNN_COREML"); - pub const OPENCL: CxxOption = cxx_option_from_feature!("opencl", "MNN_OPENCL"); - pub const OPENMP: CxxOption = cxx_option_from_feature!("openmp", "MNN_OPENMP"); - pub const OPENGL: CxxOption = cxx_option_from_feature!("opengl", "MNN_OPENGL"); - pub const CRT_STATIC: CxxOption = cxx_option_from_feature!("opengl", "MNN_WIN_RUNTIME_MT"); - pub const THREADPOOL: CxxOption = - cxx_option_from_feature!("mnn-threadpool", "MNN_USE_THREAD_POOL"); + + cxx_option_from_features! { + VULKAN => "vulkan", "MNN_VULKAN", + METAL => "metal", "MNN_METAL", + COREML => "coreml", "MNN_COREML", + OPENCL => "opencl", "MNN_OPENCL", + CRT_STATIC => "crt_static", "MNN_WIN_RUNTIME_MT", + SPARSE_COMPUTE => "sparse-compute", "MNN_USE_SPARSE_COMPUTE", + THREADPOOL => "mnn-threadpool", "MNN_USE_THREAD_POOL", + MINI_BUILD => "mini-build", "MNN_BUILD_MINI", + ARM82 => "arm82", "MNN_ARM82", + BF16 => "bf16", "MNN_SUPPORT_BF16", + AVX512 => "avx512", "MNN_AVX512", + SSE => "sse", "MNN_USE_SSE", + LOW_MEMORY => "low-memory", "MNN_LOW_MEMORY", + NEON => "neon", "MNN_USE_NEON", + CPU_WEIGHT_DEQUANT_GEMM => "cpu-weight-dequant-gemm", "MNN_CPU_WEIGHT_DEQUANT_GEMM" + } + + pub fn define(&self, build: &mut cc::Build) { + if self.enabled() { + build.define(self.name, self.cc()); + } + } pub fn new(name: &'static str, value: impl Into) -> Self { Self { @@ -519,6 +514,14 @@ impl CxxOption { } } + pub fn cc(&self) -> &str { + match &self.value { + CxxOptionValue::On => "1", + CxxOptionValue::Off => "0", + CxxOptionValue::Value(v) => v, + } + } + pub fn cmake_value(&self) -> &'static str { match &self.value { CxxOptionValue::On => "ON", @@ -543,3 +546,494 @@ impl CxxOption { } } } + +fn is_arm() -> bool { + TARGET_ARCH.starts_with("armv7") + || TARGET_ARCH.starts_with("aarch64") + || TARGET_ARCH.starts_with("arm64") +} + +fn is_x86() -> bool { + TARGET_ARCH.starts_with("x86") + // || TARGET_ARCH.starts_with("i686") + // || TARGET_ARCH.starts_with("i386") +} + +fn read_dir(input: impl AsRef) -> impl Iterator { + ignore::WalkBuilder::new(input) + .max_depth(Some(1)) + .build() + .flatten() + .map(|e| e.into_path()) +} + +pub fn mnn_cpp_build(vendor: impl AsRef) -> Result<()> { + let mut build = cc::Build::new(); + let vendor = vendor.as_ref(); + let mut includes = vec![ + vendor.join("include/"), + vendor.join("source/"), + vendor.join("schema/current/"), + vendor.join("3rd_party/"), + vendor.join("3rd_party/flatbuffers/include"), + vendor.join("3rd_party/half"), + vendor.join("3rd_party/OpenCLHeaders/"), + ]; + + build + .includes(&includes) + .cpp(true) + .static_crt(STATIC_CRT) + .static_flag(true) + .std("c++11"); + + if cfg!(unix) { + build + .flag_if_supported("-Wno-format") + .flag_if_supported("-Wno-ignored-qualifiers") + .flag_if_supported("-Wno-sign-compare") + .flag_if_supported("-Wno-unused-but-set-variable") + .flag_if_supported("-Wno-unused-function") + .flag_if_supported("-Wno-unused-lambda-capture") + .flag_if_supported("-Wno-unused-local-typedef") + .flag_if_supported("-Wno-unused-parameter") + .flag_if_supported("-Wno-unused-private-field") + .flag_if_supported("-Wno-unused-variable"); + } + if cfg!(windows) { + build + .flag_if_supported("/wd4267") + .flag_if_supported("/wd4018") + .flag_if_supported("/wd4251") + .flag_if_supported("/wd4996") + .flag_if_supported("/wd4244") + .flag_if_supported("/wd4146") + .flag_if_supported("/wd4129") + .flag_if_supported("/wd4305") + .flag_if_supported("/wd4275") + .flag_if_supported("/wd4101"); + } + + let like_msvc = build.get_compiler().is_like_msvc(); + if like_msvc { + build.flag_if_supported("/source-charset:utf-8"); + } + + // CxxOption::VULKAN.define(&mut build); + // CxxOption::COREML.define(&mut build); + CxxOption::METAL.define(&mut build); + CxxOption::OPENCL.define(&mut build); + CxxOption::CRT_STATIC.define(&mut build); + CxxOption::SPARSE_COMPUTE.define(&mut build); + CxxOption::THREADPOOL.define(&mut build); + CxxOption::MINI_BUILD.define(&mut build); + (is_arm() && has_cpu_feature("neon")).then(|| CxxOption::NEON.define(&mut build)); + (is_x86() && has_cpu_feature("sse")).then(|| CxxOption::SSE.define(&mut build)); + CxxOption::LOW_MEMORY.define(&mut build); + CxxOption::CPU_WEIGHT_DEQUANT_GEMM.define(&mut build); + + let core_files_dir = vendor.join("source").join("core"); + let core_files = ignore::Walk::new(&core_files_dir) + .flatten() + .filter(|e| e.path().extension() == Some(OsStr::new("cpp"))) + .map(|e| e.into_path()); + build.files(core_files); + + { + let cpu_files_dir = vendor.join("source").join("backend").join("cpu"); + let cpu_files = ignore::WalkBuilder::new(&cpu_files_dir) + .add(cpu_files_dir.join("compute")) + .max_depth(Some(1)) + .add_custom_ignore_filename("CPUImageProcess.hpp") + .add_custom_ignore_filename("CPUImageProcess.cpp") + .build() + .flatten() + .filter(|e| e.path().extension() == Some(OsStr::new("cpp"))) + .map(|e| e.into_path()); + + if CxxOption::ARM82.enabled() && is_arm() { + build.define("ENABLE_ARMV82", None); + } + + if is_arm() { + build.include(cpu_files_dir.join("arm")); + includes.push(cpu_files_dir.join("arm")); + arm(&mut build, cpu_files_dir.join("arm"))?; + } + + if has_cpu_feature("sse") && is_x86() && CxxOption::SSE.enabled() { + x86_64(&mut build, &includes, cpu_files_dir.join("x86_x64"))?; + } + + build.files(cpu_files); + } + + { + let cv_files_dir = vendor.join("source").join("cv"); + let cv_files = ignore::Walk::new(&cv_files_dir) + .flatten() + .filter(|e| e.path().extension() == Some(OsStr::new("cpp"))) + .map(|e| e.into_path()); + // build.include(cv_files_dir.join("schema").join("current")); + if *TARGET_OS == "macos" { + build.flag_if_supported("-fno-stack-check"); + } + build.files(cv_files); + } + + { + let extra_files = ignore::WalkBuilder::new(vendor.join("source").join("math")) + .add(vendor.join("source").join("shape")) + .add(vendor.join("source").join("geometry")) + .add(vendor.join("source").join("utils")) + .build() + .flatten() + .filter(|p| cpp_filter(p.path())) + .map(|e| e.into_path()); + build.files(extra_files); + } + + #[cfg(feature = "opencl")] + let build = opencl(build, vendor).change_context(Error)?; + #[cfg(feature = "metal")] + let build = metal(build, vendor).change_context(Error)?; + + build + .try_compile("mnn") + .change_context(Error) + .attach_printable("Failed to compile mnn")?; + Ok(()) +} + +fn arm(build: &mut cc::Build, arm_dir: impl AsRef) -> Result<&mut cc::Build> { + let arm_source_dir = arm_dir.as_ref(); + + let neon_sources: Vec = vec![arm_source_dir.join("CommonOptFunctionNeon.cpp")]; + // if CxxOption::BF16.enabled() { + // let path = arm_source_dir.join("CommonNeonBF16.cpp"); + // if path.exists() { + // neon_sources.push(path); + // } + // } + + if *TARGET_POINTER_WIDTH == 64 { + let arm64_sources_dir = arm_source_dir.join("arm64"); + let arm64_sources = ignore::Walk::new(&arm64_sources_dir) + .flatten() + .filter(|e| { + e.path().extension() == Some(OsStr::new("S")) + || e.path().extension() == Some(OsStr::new("s")) + }) + .map(|e| e.into_path()); + + // MNN_LOW_MEMORY + // MNN_CPU_WEIGHT_DEQUANT_GEMM + + build + .files(arm64_sources.chain(neon_sources)) + .define("__aarch64__", None); + } else if *TARGET_POINTER_WIDTH == 32 { + let arm32_sources_dir = arm_source_dir.join("arm32"); + let arm32_sources = ignore::Walk::new(&arm32_sources_dir) + .flatten() + .filter(|e| { + e.path().extension() == Some(OsStr::new("S")) + || e.path().extension() == Some(OsStr::new("s")) + }) + .map(|e| e.into_path()); + + // MNN_LOW_MEMORY + // MNN_CPU_WEIGHT_DEQUANT_GEMM + + build + .files(arm32_sources.chain(neon_sources)) + .define("__arm__", None); + } + Ok(build) +} + +fn x86_64<'a>( + build: &'a mut cc::Build, + includes: &'_ [PathBuf], + x86_64_dir: impl AsRef, +) -> Result<&'a mut cc::Build> { + let mnn_assembler = std::env::var("MNN_ASSEMBLER").ok(); + let like_msvc = build.get_compiler().is_like_msvc(); + let win_use_asm = like_msvc && *TARGET_POINTER_WIDTH == 64 && mnn_assembler.is_some(); + let has_avx512 = target_has_avx512(); + let x86_src_dir = x86_64_dir.as_ref(); + let mnn_x8664_src = read_dir(&x86_src_dir).filter(|p| cpp_filter(p)); + let mnn_avx_src = read_dir(x86_src_dir.join("avx")).filter(|p| cpp_filter(p) || asm_filter(p)); + let mnn_avxfma_src = + read_dir(x86_src_dir.join("avxfma")).filter(|p| cpp_filter(p) || asm_filter(p)); + let mnn_sse_src = read_dir(x86_src_dir.join("sse")).filter(|p| cpp_filter(p)); + let mnn_avx512_vnni_src = x86_src_dir.join("avx512/GemmInt8_VNNI.cpp"); + let mnn_avx512_src = read_dir(x86_src_dir.join("avx512")) + .filter(|p| cpp_filter(p) || asm_filter(p)) + .filter(|p| p != &mnn_avx512_vnni_src); + + if has_avx512 && CxxOption::AVX512.enabled() && (!like_msvc || win_use_asm) { + let mnn_avx512 = cc::Build::new() + .files(mnn_avx512_src) + .static_crt(STATIC_CRT) + .static_flag(true) + .define("MNN_USE_SSE", None) + .define("MNN_X86_USE_ASM", None) + .tap_mut(|build| { + if build.get_compiler().is_like_msvc() { + build.flag_if_supported("/arch:AVX512"); + } else { + // target_compile_options(MNNAVX512 PRIVATE -m64 -mavx512f -mavx512dq -mavx512vl -mavx512bw -mfma) + build + .flag_if_supported("-m64") + .flag_if_supported("-mavx512f") + .flag_if_supported("-mavx512dq") + .flag_if_supported("-mavx512vl") + .flag_if_supported("-mavx512bw") + .flag_if_supported("-mfma"); + } + }) + .try_compile_intermediates() + .change_context(Error)?; + build.objects(mnn_avx512); + let mnn_avx512_vnni = true; + if mnn_avx512_vnni { + let mnn_avx512_vnni = cc::Build::new() + .file(mnn_avx512_vnni_src) + .define("MNN_USE_SSE", None) + .define("MNN_X86_USE_ASM", None) + .tap_mut(|build| { + if build.get_compiler().is_like_msvc() { + build.flag_if_supported("/arch:AVX512"); + } else { + // target_compile_options(MNNAVX512_VNNI PRIVATE -m64 -mavx512f -mavx512dq -mavx512vl -mavx512bw -mfma -mavx512vnni) + build + .flag_if_supported("-m64") + .flag_if_supported("-mavx512f") + .flag_if_supported("-mavx512dq") + .flag_if_supported("-mavx512vl") + .flag_if_supported("-mavx512bw") + .flag_if_supported("-mfma") + .flag_if_supported("-mavx512vnni"); + } + }) + .try_compile_intermediates() + .change_context(Error)?; + build.objects(mnn_avx512_vnni); + } + } + + let mnn_sse = cc::Build::new() + .cpp(true) + .std("c++11") + .includes(includes) + .files(mnn_sse_src) + .static_crt(STATIC_CRT) + .static_flag(true) + .define("MNN_USE_SSE", None) + .tap_mut(|build| { + if !like_msvc { + build.flag_if_supported("-msse4.1"); + } + CxxOption::LOW_MEMORY.define(build); + }) + .try_compile_intermediates() + .change_context(Error) + .attach_printable("Failed to build sse extensions")?; + + let mnn_avx = cc::Build::new() + .cpp(true) + .std("c++11") + .includes(includes) + .files(mnn_avx_src) + .static_crt(STATIC_CRT) + .static_flag(true) + .define("MNN_USE_SSE", None) + .tap_mut(|build| { + if like_msvc { + build.flag_if_supported("/arch:AVX"); + } else { + build + .flag_if_supported("-mavx2") + .define("MNN_X86_USE_ASM", None); + } + CxxOption::LOW_MEMORY.define(build); + }) + .try_compile_intermediates() + .change_context(Error)?; + + let mnn_avxfma = cc::Build::new() + .cpp(true) + .std("c++11") + .includes(includes) + .files(mnn_avxfma_src) + .static_crt(STATIC_CRT) + .static_flag(true) + .define("MNN_USE_SSE", None) + .tap_mut(|build| { + if like_msvc { + build.flag_if_supported("/arch:AVX2"); + } else { + build + .flag_if_supported("-mavx2") + .flag_if_supported("-mfma") + .define("MNN_X86_USE_ASM", None); + } + CxxOption::LOW_MEMORY.define(build); + CxxOption::BF16.define(build) + }) + .try_compile_intermediates() + .change_context(Error)?; + + let mnn_x8664 = cc::Build::new() + .cpp(true) + .std("c++11") + .includes(includes) + .files(mnn_x8664_src) + .static_crt(STATIC_CRT) + .static_flag(true) + .tap_mut(|build| { + CxxOption::LOW_MEMORY.define(build); + CxxOption::SSE.define(build); + CxxOption::CPU_WEIGHT_DEQUANT_GEMM.define(build); + if has_avx512 && CxxOption::AVX512.enabled() && (!like_msvc || win_use_asm) { + CxxOption::AVX512.define(build); + } + }) + .try_compile_intermediates() + .change_context(Error)?; + + build.objects(mnn_sse); + build.objects(mnn_x8664); + build.objects(mnn_avx); + build.objects(mnn_avxfma); + + has_avx512.then(|| { + CxxOption::AVX512.define(build); + }); + + Ok(build) +} + +fn target_has_avx512() -> bool { + const AVX_PRG: &str = r#" +#ifndef __AVX512F__ +#error "AVX-512 support is required to compile this program." +#endif +int main() {return 0;} "#; + let out_dir: PathBuf = std::env::var("OUT_DIR") + .expect("OUT_DIR must be set in build.rs") + .into(); + std::fs::write(out_dir.join("test.c"), AVX_PRG).expect("Failed to write to out_dir"); + cc::Build::new() + .file("test.c") + .cargo_warnings(false) + .try_compile("avx512") + .is_ok() +} + +fn cpp_filter(path: impl AsRef) -> bool { + path.as_ref().extension() == Some(OsStr::new("cpp")) + || path.as_ref().extension() == Some(OsStr::new("cc")) +} + +fn asm_filter(path: impl AsRef) -> bool { + path.as_ref().extension() == Some(OsStr::new("S")) + || path.as_ref().extension() == Some(OsStr::new("s")) +} + +pub trait HasExtension { + fn has_extension>(&self, extensions: impl IntoIterator) -> bool; + fn has_extension_ignore_case>( + &self, + extension: impl IntoIterator, + ) -> bool; +} + +impl> HasExtension for P { + fn has_extension>(&self, extensions: impl IntoIterator) -> bool { + let path_ext = self.as_ref().extension(); + extensions + .into_iter() + .any(|ext| path_ext == Some(ext.as_ref())) + } + fn has_extension_ignore_case>( + &self, + extension: impl IntoIterator, + ) -> bool { + let path_ext = self.as_ref().extension().map(|ext| ext.as_encoded_bytes()); + extension.into_iter().any(|ext| { + let ext = ext.as_ref().as_encoded_bytes(); + path_ext + .map(|p| p.eq_ignore_ascii_case(ext)) + .unwrap_or_default() + }) + } +} + +pub fn metal(mut build: cc::Build, vendor: impl AsRef) -> Result { + let metal_source_dir = vendor.as_ref().join("source/backend/metal"); + let metal_files = ignore::Walk::new(&metal_source_dir) + .flatten() + .filter(|e| e.path().has_extension(["mm", "cpp"])) + .map(ignore::DirEntry::into_path) + .chain(core::iter::once( + metal_source_dir.join("MetalOPRegister.mm"), + )); + + cc_builder() + .define(CxxOption::METAL.name, CxxOption::METAL.cc()) + .files(metal_files) + .includes(mnn_includes(vendor)) + .flag("-fobjc-arc") + .define("MNN_METAL_ENABLED", "1") + .try_compile("MNNMetal") + .change_context(Error) + .attach_printable("Failed to compile MNNMetal")?; + build.define("MNN_METAL_ENABLED", "1"); + Ok(build) +} + +pub fn opencl(mut build: cc::Build, vendor: impl AsRef) -> Result { + let source = vendor.as_ref().join("source"); + let backend = source.join("backend"); + let opencl_files_dir = backend.join("opencl"); + let opencl_files = ignore::Walk::new(&opencl_files_dir) + .flatten() + .filter(|e| e.path().has_extension(["cpp"])) + .map(|e| e.into_path()); + let ocl_includes = opencl_files_dir.join("schema").join("current"); + cc_builder() + .define(CxxOption::OPENCL.name, CxxOption::OPENCL.cc()) + .includes(mnn_includes(vendor)) + .include(ocl_includes.clone()) + .define("MNN_OPENCL_ENABLED", "1") + .files(opencl_files.chain([opencl_files_dir.join("execution/cl/opencl_program.cc")])) + .try_compile("MNNOpenCL") + .change_context(Error) + .attach_printable("Failed to build MNNOpenCL")?; + build.define("MNN_OPENCL_ENABLED", "1"); + Ok(build) +} + +pub fn mnn_includes(vendor: impl AsRef) -> Vec { + let vendor = vendor.as_ref(); + vec![ + vendor.join("include/"), + vendor.join("source/"), + vendor.join("schema/current/"), + vendor.join("3rd_party/"), + vendor.join("3rd_party/flatbuffers/include"), + vendor.join("3rd_party/half"), + vendor.join("3rd_party/OpenCLHeaders/"), + ] +} + +pub fn cc_builder() -> cc::Build { + cc::Build::new() + .cpp(true) + .static_crt(STATIC_CRT) + .static_flag(true) + .std("c++11") + .to_owned() +} diff --git a/mnn-sys/src/tracing.rs b/mnn-sys/src/tracing.rs index 7f298db..88b89c5 100644 --- a/mnn-sys/src/tracing.rs +++ b/mnn-sys/src/tracing.rs @@ -131,12 +131,7 @@ impl MnnCallsite { } #[no_mangle] -extern "C" fn mnn_ffi_emit( - file: *const c_char, - line: libc::size_t, - level: Level, - message: *const c_char, -) { +extern "C" fn mnn_ffi_emit(file: *const c_char, line: usize, level: Level, message: *const c_char) { std::panic::catch_unwind(|| { let file: &'static str = unsafe { core::ffi::CStr::from_ptr(file) diff --git a/mnn-sys/vendor b/mnn-sys/vendor index b03cd53..707b8a4 160000 --- a/mnn-sys/vendor +++ b/mnn-sys/vendor @@ -1 +1 @@ -Subproject commit b03cd53191c586cc94a94b76f85b904b654d8d78 +Subproject commit 707b8a41b25e3d0b7c4a39cd81109d7074ca3c28 diff --git a/src/backend.rs b/src/backend.rs index 7f9a45e..b1bd2b8 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -317,7 +317,7 @@ impl BackendConfig { /// # Safety /// This just binds to the underlying unsafe api and should be used only if you know what you /// are doing - pub unsafe fn set_shared_context(&mut self, shared_context: *mut libc::c_void) { + pub unsafe fn set_shared_context(&mut self, shared_context: *mut core::ffi::c_void) { unsafe { mnn_sys::mnnbc_set_shared_context(self.inner, shared_context); } @@ -326,7 +326,7 @@ impl BackendConfig { /// # Safety /// This just binds to the underlying unsafe api and should be used only if you know what you /// are doing - pub unsafe fn with_shared_context(mut self, shared_context: *mut libc::c_void) -> Self { + pub unsafe fn with_shared_context(mut self, shared_context: *mut core::ffi::c_void) -> Self { self.set_shared_context(shared_context); self } diff --git a/src/interpreter.rs b/src/interpreter.rs index 8526bd3..3ca4806 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -23,7 +23,7 @@ impl Default for TensorCallback { } impl TensorCallback { - pub(crate) fn from_ptr(f: *mut libc::c_void) -> Self { + pub(crate) fn from_ptr(f: *mut core::ffi::c_void) -> Self { debug_assert!(!f.is_null()); unsafe { Self { @@ -32,8 +32,8 @@ impl TensorCallback { } } - pub(crate) fn into_ptr(self) -> *mut libc::c_void { - Arc::into_raw(self.inner) as *mut libc::c_void + pub(crate) fn into_ptr(self) -> *mut core::ffi::c_void { + Arc::into_raw(self.inner) as *mut core::ffi::c_void } #[cfg(test)] @@ -134,7 +134,7 @@ impl SessionMode { #[derive(Debug)] pub struct Interpreter { pub(crate) inner: *mut mnn_sys::Interpreter, - pub(crate) __marker: PhantomData<()>, + pub(crate) __marker: PhantomData>, } unsafe impl Send for Interpreter {} @@ -187,7 +187,7 @@ impl Interpreter { /// /// **Warning:** /// It should be called before create session! - pub fn set_session_mode(&mut self, mode: SessionMode) { + pub fn set_session_mode(&self, mode: SessionMode) { unsafe { mnn_sys::Interpreter_setSessionMode(self.inner, mode.to_mnn_sys()) } } @@ -255,9 +255,9 @@ impl Interpreter { /// /// return: the created session pub fn create_session( - &mut self, + &self, schedule: crate::ScheduleConfig, - ) -> Result { + ) -> Result> { profile!("Creating session"; { let session = unsafe { mnn_sys::Interpreter_createSession(self.inner, schedule.inner) }; assert!(!session.is_null()); @@ -274,7 +274,7 @@ impl Interpreter { /// # Safety /// This function is marked unsafe since it's not clear what the safety guarantees are right /// now. With a simple test it caused a segfault so it's marked unsafe - pub unsafe fn release_model(&mut self) { + pub unsafe fn release_model(&self) { unsafe { mnn_sys::Interpreter_releaseModel(self.inner) } } @@ -284,7 +284,7 @@ impl Interpreter { /// /// return: the created session pub fn create_multipath_session( - &mut self, + &self, schedule: impl IntoIterator, ) -> Result { profile!("Creating multipath session"; { @@ -452,7 +452,7 @@ impl Interpreter { } /// Run a session - pub fn run_session(&mut self, session: &crate::session::Session) -> Result<()> { + pub fn run_session(&self, session: &crate::session::Session) -> Result<()> { profile!("Running session"; { let ret = unsafe { mnn_sys::Interpreter_runSession(self.inner, session.inner) }; ensure!( @@ -473,13 +473,13 @@ impl Interpreter { /// /// `sync` : synchronously wait for finish of execution or not. pub fn run_session_with_callback( - &mut self, + &self, session: &crate::session::Session, before: impl Fn(&[RawTensor], OperatorInfo) -> bool + 'static, end: impl Fn(&[RawTensor], OperatorInfo) -> bool + 'static, sync: bool, ) -> Result<()> { - let sync = sync as libc::c_int; + let sync = sync as core::ffi::c_int; let before = TensorCallback::from(before).into_ptr(); let end = TensorCallback::from(end).into_ptr(); let ret = unsafe { @@ -516,7 +516,7 @@ impl Interpreter { /// The API should be called before create session. /// /// Key Depercerate, keeping for future use! - pub fn set_cache_file(&mut self, path: impl AsRef, key_size: usize) -> Result<()> { + pub fn set_cache_file(&self, path: impl AsRef, key_size: usize) -> Result<()> { let path = path.as_ref(); let path = dunce::simplified(path); let path = path.to_str().ok_or_else(|| error!(ErrorKind::AsciiError))?; @@ -526,7 +526,7 @@ impl Interpreter { } /// Update cache file - pub fn update_cache_file(&mut self, session: &mut crate::session::Session) -> Result<()> { + pub fn update_cache_file(&self, session: &mut crate::session::Session) -> Result<()> { MNNError::from_error_code(unsafe { mnn_sys::Interpreter_updateCacheFile(self.inner, session.inner) }); @@ -571,7 +571,7 @@ impl Interpreter { self.inner, session.inner, mnn_sys::cpp::MNN_Interpreter_SessionInfoCode_FLOPS as _, - flop_ptr.cast::(), + flop_ptr.cast::(), ) }; ensure!( @@ -622,18 +622,18 @@ pub enum ResizeStatus { #[no_mangle] extern "C" fn rust_closure_callback_runner_op( - f: *mut libc::c_void, + f: *mut core::ffi::c_void, tensors: *const *mut mnn_sys::Tensor, tensor_count: usize, - op: *mut libc::c_void, -) -> libc::c_int { + op: *mut core::ffi::c_void, +) -> core::ffi::c_int { let tensors = unsafe { std::slice::from_raw_parts(tensors.cast(), tensor_count) }; let f: TensorCallback = TensorCallback::from_ptr(f); let op = OperatorInfo { inner: op.cast(), __marker: PhantomData, }; - let ret = f(tensors, op) as libc::c_int; + let ret = f(tensors, op) as core::ffi::c_int; core::mem::forget(f); ret @@ -642,7 +642,7 @@ extern "C" fn rust_closure_callback_runner_op( /// A struct that holds information about an operator #[repr(transparent)] pub struct OperatorInfo<'op> { - pub(crate) inner: *mut libc::c_void, + pub(crate) inner: *mut core::ffi::c_void, pub(crate) __marker: PhantomData<&'op ()>, } @@ -674,12 +674,11 @@ impl OperatorInfo<'_> { } #[test] -#[ignore = "This test doesn't work in CI"] fn test_run_session_with_callback_info_api() { let file = Path::new("tests/assets/realesr.mnn") .canonicalize() .unwrap(); - let mut interpreter = Interpreter::from_file(&file).unwrap(); + let interpreter = Interpreter::from_file(&file).unwrap(); let session = interpreter.create_session(ScheduleConfig::new()).unwrap(); interpreter .run_session_with_callback( @@ -692,12 +691,11 @@ fn test_run_session_with_callback_info_api() { } #[test] -#[ignore = "This test doesn't work in CI"] fn check_whether_sync_actually_works() { let file = Path::new("tests/assets/realesr.mnn") .canonicalize() .unwrap(); - let mut interpreter = Interpreter::from_file(&file).unwrap(); + let interpreter = Interpreter::from_file(&file).unwrap(); let session = interpreter.create_session(ScheduleConfig::new()).unwrap(); let time = std::time::Instant::now(); interpreter @@ -722,14 +720,14 @@ fn check_whether_sync_actually_works() { assert!((time - time2) > std::time::Duration::from_millis(50)); } -#[test] -#[ignore = "Fails on CI"] -fn try_to_drop_interpreter_before_session() { - let file = Path::new("tests/assets/realesr.mnn") - .canonicalize() - .unwrap(); - let mut interpreter = Interpreter::from_file(&file).unwrap(); - let session = interpreter.create_session(ScheduleConfig::new()).unwrap(); - drop(interpreter); - drop(session); -} +// Impossible to compile +// #[test] +// fn try_to_drop_interpreter_before_session() { +// let file = Path::new("tests/assets/realesr.mnn") +// .canonicalize() +// .unwrap(); +// let mut interpreter = Interpreter::from_file(&file).unwrap(); +// let session = interpreter.create_session(ScheduleConfig::new()).unwrap(); +// drop(interpreter); +// drop(session); +// } diff --git a/src/lib.rs b/src/lib.rs index f990720..a0da8f9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,8 +93,8 @@ pub use ffi::MapType; pub mod prelude { pub use crate::error::*; pub(crate) use crate::profile::profile; + pub use core::ffi::*; pub use core::marker::PhantomData; pub use error_stack::{Report, ResultExt}; - pub use libc::*; pub use mnn_sys::{HalideType, MapType}; } diff --git a/src/schedule.rs b/src/schedule.rs index 6fe6955..f32666c 100644 --- a/src/schedule.rs +++ b/src/schedule.rs @@ -16,7 +16,6 @@ use crate::{prelude::*, BackendConfig}; /// - `CPU`: Use the CPU for computation. /// - `Metal`: Use the Metal backend for computation (requires the `metal` feature). /// - `OpenCL`: Use the OpenCL backend for computation (requires the `opencl` feature). -/// - `OpenGL`: Use the OpenGL backend for computation (requires the `opengl` feature). /// - `Vulkan`: Use the Vulkan backend for computation (requires the `vulkan` feature). /// - `CoreML`: Use the CoreML backend for computation (requires the `coreml` feature). /// @@ -68,8 +67,6 @@ impl ForwardType { ForwardType::Metal => MNNForwardType::MNN_FORWARD_METAL, #[cfg(feature = "opencl")] ForwardType::OpenCL => MNNForwardType::MNN_FORWARD_OPENCL, - #[cfg(feature = "opengl")] - ForwardType::OpenGL => MNNForwardType::MNN_FORWARD_OPENGL, #[cfg(feature = "vulkan")] ForwardType::Vulkan => MNNForwardType::MNN_FORWARD_VULKAN, #[cfg(feature = "coreml")] @@ -86,8 +83,6 @@ impl ForwardType { MNNForwardType::MNN_FORWARD_METAL => ForwardType::Metal, #[cfg(feature = "opencl")] MNNForwardType::MNN_FORWARD_OPENCL => ForwardType::OpenCL, - #[cfg(feature = "opengl")] - MNNForwardType::MNN_FORWARD_OPENGL => ForwardType::OpenGL, #[cfg(feature = "vulkan")] MNNForwardType::MNN_FORWARD_VULKAN => ForwardType::Vulkan, #[cfg(feature = "coreml")] @@ -106,8 +101,6 @@ impl ForwardType { "metal", #[cfg(feature = "opencl")] "opencl", - #[cfg(feature = "opengl")] - "opengl", #[cfg(feature = "vulkan")] "vulkan", #[cfg(feature = "coreml")] @@ -125,8 +118,6 @@ impl ForwardType { ForwardType::Metal => "metal", #[cfg(feature = "opencl")] ForwardType::OpenCL => "opencl", - #[cfg(feature = "opengl")] - ForwardType::OpenGL => "opengl", #[cfg(feature = "vulkan")] ForwardType::Vulkan => "vulkan", #[cfg(feature = "coreml")] @@ -147,8 +138,6 @@ impl core::str::FromStr for ForwardType { "metal" => Ok(ForwardType::Metal), #[cfg(feature = "opencl")] "opencl" => Ok(ForwardType::OpenCL), - #[cfg(feature = "opengl")] - "opengl" => Ok(ForwardType::OpenGL), #[cfg(feature = "vulkan")] "vulkan" => Ok(ForwardType::Vulkan), #[cfg(feature = "coreml")] diff --git a/src/session.rs b/src/session.rs index 9e2fae4..7113e7d 100644 --- a/src/session.rs +++ b/src/session.rs @@ -4,7 +4,7 @@ use crate::prelude::*; /// /// Inference unit. multiple sessions could share one net/interpreter. #[derive(Debug)] -pub struct Session { +pub struct Session<'i> { /// Pointer to the underlying MNN session. pub(crate) inner: *mut mnn_sys::Session, /// Pointer to the underlying MNN interpreter @@ -17,7 +17,7 @@ pub struct Session { /// Internal session configurations. pub(crate) __session_internals: crate::SessionInternals, /// Marker to ensure the struct is not Send or Sync. - pub(crate) __marker: PhantomData<()>, + pub(crate) __marker: PhantomData<&'i ()>, } /// Enum representing the internal configurations of a session. @@ -29,8 +29,8 @@ pub enum SessionInternals { MultiSession(crate::ScheduleConfigs), } -impl Session { - /// Calls the destroy function on the underlying MNN session. +impl Session<'_> { + /// Calls the destructor for the underlying MNN session. pub fn destroy(&mut self) { unsafe { mnn_sys::Interpreter_releaseSession(self.net, self.inner); @@ -39,7 +39,7 @@ impl Session { } } -impl Drop for Session { +impl Drop for Session<'_> { /// Custom drop implementation to ensure the underlying MNN session is properly destroyed. fn drop(&mut self) { self.destroy(); diff --git a/tests/backend.rs b/tests/backend.rs index 4cde667..787313a 100644 --- a/tests/backend.rs +++ b/tests/backend.rs @@ -7,6 +7,7 @@ use tracing_test::traced_test; #[cfg(feature = "coreml")] #[test] #[traced_test] +#[cfg(feature = "coreml")] fn compare_cpu_and_coreml_outputs() { let mut net = mnn::Interpreter::from_file("tests/assets/realesr.mnn").unwrap(); let cpu_config = ScheduleConfig::new(); diff --git a/tests/basic.rs b/tests/basic.rs index 420ab95..e72cc8b 100644 --- a/tests/basic.rs +++ b/tests/basic.rs @@ -6,20 +6,20 @@ use mnn::ForwardType; fn test_basic_cpu() { test_basic(ForwardType::CPU).unwrap(); } + #[cfg(feature = "metal")] #[test] -#[ignore = "Doesn't work on ci"] fn test_basic_metal() { test_basic(ForwardType::Metal).unwrap(); } + #[cfg(feature = "opencl")] #[test] -#[ignore = "Doesn't work on ci"] fn test_basic_opencl() -> Result<(), Box> { let backend = ForwardType::OpenCL; let realesr = std::path::Path::new("tests/assets/realesr.mnn"); - let mut net = mnn::Interpreter::from_file(realesr)?; + let net = mnn::Interpreter::from_file(realesr)?; net.set_cache_file(realesr.with_extension("cache"), 128)?; let mut config = ScheduleConfig::new(); config.set_type(backend); @@ -46,16 +46,12 @@ fn test_basic_opencl() -> Result<(), Box> { // drop(net); Ok(()) } + #[cfg(feature = "coreml")] #[test] fn test_basic_coreml() { test_basic(ForwardType::CoreML).unwrap(); } -#[cfg(feature = "opengl")] -#[test] -fn test_basic_opengl() { - test_basic(ForwardType::OpenGL).unwrap(); -} #[test] #[ignore = "takes too long and unreliable on CI"] diff --git a/tests/common.rs b/tests/common.rs index 57a51cb..8064d09 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -27,7 +27,7 @@ impl AsRef<[u8]> for Model { #[allow(dead_code)] pub fn test_basic(backend: ForwardType) -> Result<()> { - let mut net = mnn::Interpreter::from_file("tests/assets/realesr.mnn")?; + let net = mnn::Interpreter::from_file("tests/assets/realesr.mnn")?; let mut config = ScheduleConfig::new(); config.set_type(backend); let session = net.create_session(config)?; @@ -51,7 +51,7 @@ pub fn test_basic(backend: ForwardType) -> Result<()> { pub fn test_multipath_session(backend: ForwardType, backend2: ForwardType) -> Result<()> { use mnn::BackendConfig; - let mut net = mnn::Interpreter::from_bytes(Model::new())?; + let net = mnn::Interpreter::from_bytes(Model::new())?; let mut config = ScheduleConfig::new(); config.set_type(backend); config.set_backup_type(backend); diff --git a/tests/resizing.rs b/tests/resizing.rs index f3e4753..486bcbf 100644 --- a/tests/resizing.rs +++ b/tests/resizing.rs @@ -4,11 +4,13 @@ use common::*; #[test] pub fn test_resizing() -> Result<()> { let model = std::fs::read("tests/assets/resizing.mnn").expect("No resizing model"); - let mut net = Interpreter::from_bytes(&model).unwrap(); + let net = Interpreter::from_bytes(&model).unwrap(); net.set_cache_file("resizing.cache", 128)?; - let config = ScheduleConfig::default(); + let mut config = ScheduleConfig::default(); #[cfg(feature = "opencl")] config.set_type(ForwardType::OpenCL); + #[cfg(not(feature = "opencl"))] + config.set_type(ForwardType::CPU); let mut session = net.create_session(config).unwrap(); net.update_cache_file(&mut session)?; diff --git a/tests/segfault.rs b/tests/segfault.rs index d061941..367ac56 100644 --- a/tests/segfault.rs +++ b/tests/segfault.rs @@ -6,7 +6,7 @@ fn test_segfault_case_1_() -> Result<(), Box> { let backend = ForwardType::OpenCL; let realesr = std::path::Path::new("tests/assets/realesr.mnn"); - let mut net = mnn::Interpreter::from_file(realesr)?; + let net = mnn::Interpreter::from_file(realesr)?; net.set_cache_file(realesr.with_extension("cache"), 128)?; let mut config = ScheduleConfig::new(); config.set_type(backend); @@ -28,43 +28,40 @@ fn test_segfault_case_1_() -> Result<(), Box> { } #[test] -#[ignore] -pub fn test_resizing() { +pub fn test_segfault_case_2_() { use mnn::*; let model = std::fs::read("tests/assets/resizing.mnn").expect("No resizing model"); - let mut net = Interpreter::from_bytes(&model).unwrap(); + let net = Interpreter::from_bytes(&model).unwrap(); let config = ScheduleConfig::default(); let mut session = net.create_session(config).unwrap(); - loop { - let inputs = net.inputs(&session); - for tensor_info in inputs.iter() { - let mut tensor = unsafe { tensor_info.tensor_unresized::() }.unwrap(); - let mut shape = tensor.shape().as_ref().to_vec(); - dbg!(&shape); - shape.iter_mut().for_each(|v| { - if *v == -1 { - *v = 3; - } - }); - dbg!(&shape); - net.resize_tensor(&mut tensor, &shape); - } - drop(inputs); + let inputs = net.inputs(&session); + for tensor_info in inputs.iter() { + let mut tensor = unsafe { tensor_info.tensor_unresized::() }.unwrap(); + let mut shape = tensor.shape().as_ref().to_vec(); + dbg!(&shape); + shape.iter_mut().for_each(|v| { + if *v == -1 { + *v = 3; + } + }); + dbg!(&shape); + net.resize_tensor(&mut tensor, &shape); + } + drop(inputs); - net.resize_session(&mut session); - let inputs = net.inputs(&session); - for tensor_info in inputs.iter() { - let tensor = tensor_info.tensor::().unwrap(); - println!( - "{:13}: {:>13}", - tensor_info.name(), - format!("{:?}", tensor.shape()) - ); - let mut host = tensor.create_host_tensor_from_device(false); - host.host_mut().fill(1.0); - } - drop(inputs); - net.run_session(&session).unwrap(); + net.resize_session(&mut session); + let inputs = net.inputs(&session); + for tensor_info in inputs.iter() { + let tensor = tensor_info.tensor::().unwrap(); + println!( + "{:13}: {:>13}", + tensor_info.name(), + format!("{:?}", tensor.shape()) + ); + let mut host = tensor.create_host_tensor_from_device(false); + host.host_mut().fill(1.0); } + drop(inputs); + net.run_session(&session).unwrap(); } diff --git a/tools/bencher/src/main.rs b/tools/bencher/src/main.rs index 0ec7af0..87f9f49 100644 --- a/tools/bencher/src/main.rs +++ b/tools/bencher/src/main.rs @@ -306,7 +306,7 @@ pub fn main() -> Result<()> { // let indicatif_layer = IndicatifLayer::new(); tracing_subscriber::registry() .with(cli.verbose.tracing_level_filter()) - // .with(tracing_subscriber::fmt::layer().with_writer(Term::stderr)) + .with(tracing_subscriber::fmt::layer().with_writer(Term::stderr)) .init(); match cli.subcommand { @@ -598,6 +598,7 @@ pub fn bench( bar.set_message(format!("Running inference {c}")); not_terminal.then(|| eprintln!("Running inference {c}")); net.run_session(&session).cc(BenchError)?; + net.wait(&session); } Ok(()) })?; diff --git a/tools/cachix/Makefile b/tools/cachix/Makefile new file mode 100644 index 0000000..21047be --- /dev/null +++ b/tools/cachix/Makefile @@ -0,0 +1,15 @@ +.PHONY: checks push push-all + +all: push-all + +checks: + nix flake check + +push: + cachix watch-exec mnn-rs -- nix flake check + +push-all: + cachix watch-exec mnn-rs -- nix flake check --system x86_64-linux + cachix watch-exec mnn-rs -- nix flake check --system aarch64-darwin + +