From 43a04d4fefa193938d033443c2dd0cf259e4990d Mon Sep 17 00:00:00 2001 From: Johnnie Birch <45402135+jlb6740@users.noreply.github.com> Date: Tue, 10 Sep 2019 18:17:19 -0700 Subject: [PATCH] Adds perf jitdump support Patch adds support for the perf jitdump file specification. With this patch it should be possible to see profile data for code generated and maped at runtime. Specifically the patch adds support for the JIT_CODE_LOAD and the JIT_DEBUG_INFO record as described in the specification. Dumping jitfiles is enabled with the --jitdump flag. When the -g flag is also used there is an attempt to dump file and line number information where this option would be most useful when the WASM file already includes DWARF debug information. The generation of the jitdump files has been tested on only a few wasm files. This patch is expected to be useful/serviceable where currently there is no means for jit profiling, but future patches may benefit line mapping and add support for additional jitdump record types. Usage Example: Record sudo perf record -k 1 -e instructions:u target/debug/wasmtime -g --jitdump test.wasm Combine sudo perf inject -v -j -i perf.data -o perf.jit.data Report sudo perf report -i perf.jit.data -F+period,srcline --- Cargo.lock | 388 ++++++++++++----- Cargo.toml | 2 + crates/api/Cargo.toml | 2 + crates/api/src/module.rs | 1 + crates/api/src/runtime.rs | 19 +- crates/jit/Cargo.toml | 2 + crates/jit/src/code_memory.rs | 19 + crates/jit/src/compiler.rs | 11 + crates/jit/src/instantiate.rs | 31 +- crates/profiling/Cargo.toml | 27 ++ crates/profiling/src/jitdump.rs | 720 ++++++++++++++++++++++++++++++++ crates/profiling/src/lib.rs | 84 ++++ crates/runtime/Cargo.toml | 1 + src/lib.rs | 15 +- tests/instantiate.rs | 2 +- 15 files changed, 1216 insertions(+), 108 deletions(-) create mode 100644 crates/profiling/Cargo.toml create mode 100644 crates/profiling/src/jitdump.rs create mode 100644 crates/profiling/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 358b85c32068..82cea6dee64f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,10 +1,16 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "adler32" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" + [[package]] name = "aho-corasick" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f56c476256dc249def911d6f7580b5fc7e875895b5d7ee88f5d602208035744" +checksum = "743ad5a418686aad3b87fd14c43badd828cf26e214a00f92a384291cf22e1811" dependencies = [ "memchr", ] @@ -85,9 +91,9 @@ checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" [[package]] name = "backtrace" -version = "0.3.43" +version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f80256bc78f67e7df7e36d77366f636ed976895d91fe2ab9efa3973e8fe8c4f" +checksum = "e4036b9bf40f3cf16aba72a3d65e8a520fc4bafcdc7079aea8f848c58c5b5536" dependencies = [ "backtrace-sys", "cfg-if", @@ -159,8 +165,8 @@ dependencies = [ "lazycell", "log", "peeking_take_while", - "proc-macro2", - "quote", + "proc-macro2 1.0.8", + "quote 1.0.2", "regex", "rustc-hash", "shlex", @@ -213,9 +219,9 @@ checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" [[package]] name = "byteorder" -version = "1.3.2" +version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" [[package]] name = "c2-chacha" @@ -354,7 +360,7 @@ dependencies = [ "log", "serde", "smallvec", - "target-lexicon", + "target-lexicon 0.10.0", "thiserror", ] @@ -392,7 +398,7 @@ dependencies = [ "cranelift-codegen", "log", "smallvec", - "target-lexicon", + "target-lexicon 0.10.0", ] [[package]] @@ -403,7 +409,7 @@ checksum = "564ee82268bc25b914fcf331edfc2452f2d9ca34f976b187b4ca668beba250c8" dependencies = [ "cranelift-codegen", "raw-cpuid", - "target-lexicon", + "target-lexicon 0.10.0", ] [[package]] @@ -421,6 +427,15 @@ dependencies = [ "wasmparser 0.48.2", ] +[[package]] +name = "crc32fast" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-deque" version = "0.7.2" @@ -472,8 +487,8 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd8ce37ad4184ab2ce004c33bf6379185d3b1c95801cab51026bd271bf68eedc" dependencies = [ - "quote", - "syn", + "quote 1.0.2", + "syn 1.0.14", ] [[package]] @@ -491,8 +506,8 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dab2f0544254a47cabc58956cc7ebda74c3b796bb2761e3fe8f29fdde632ad95" dependencies = [ - "proc-macro2", - "syn", + "proc-macro2 1.0.8", + "syn 1.0.14", "synstructure", ] @@ -537,9 +552,9 @@ dependencies = [ "byteorder", "lazy_static", "owning_ref", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.14", ] [[package]] @@ -612,15 +627,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74b9ed6159e4a6212c61d9c6a86bee01876b192a64accecf58d5b5ae3b667b52" dependencies = [ "anyhow", - "goblin", + "goblin 0.1.3", "indexmap", "log", - "scroll", + "scroll 0.10.1", "string-interner", - "target-lexicon", + "target-lexicon 0.10.0", "thiserror", ] +[[package]] +name = "failure" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9" +dependencies = [ + "failure_derive", +] + +[[package]] +name = "failure_derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08" +dependencies = [ + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.14", + "synstructure", +] + [[package]] name = "fake-simd" version = "0.1.2" @@ -655,6 +691,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "flate2" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bd6d6f4752952feb71363cffc9ebac9411b75b87c6ab6058c40c8900cf43c0f" +dependencies = [ + "cfg-if", + "crc32fast", + "libc", + "miniz_oxide", +] + [[package]] name = "gcc" version = "0.3.55" @@ -687,9 +735,9 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a36606a68532b5640dc86bb1f33c64b45c4682aad4c50f3937b317ea387f3d6" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.14", ] [[package]] @@ -721,6 +769,28 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +[[package]] +name = "goblin" +version = "0.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f55d53401eb2fd30afd025c570b1946b6966344acf21b42e31286f3bf89e6a8" +dependencies = [ + "log", + "plain", + "scroll 0.9.2", +] + +[[package]] +name = "goblin" +version = "0.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3fa261d919c1ae9d1e4533c4a2f99e10938603c4208d56c05bec7a872b661b0" +dependencies = [ + "log", + "plain", + "scroll 0.9.2", +] + [[package]] name = "goblin" version = "0.1.3" @@ -729,7 +799,7 @@ checksum = "3081214398d39e4bd7f2c1975f0488ed04614ffdd976c6fc7a0708278552c0da" dependencies = [ "log", "plain", - "scroll", + "scroll 0.10.1", ] [[package]] @@ -767,9 +837,9 @@ checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" [[package]] name = "indexmap" -version = "1.3.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b54058f0a6ff80b6803da8faf8997cde53872b38f4023728f6830b06cd3c0dc" +checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" dependencies = [ "autocfg 1.0.0", ] @@ -791,9 +861,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b714fc08d0961716390977cdff1536234415ac37b509e34e5a983def8340fb75" dependencies = [ "proc-macro-hack", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.14", "unindent", ] @@ -814,9 +884,9 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a8e30575afe28eea36a9a39136b70b2fb6b0dd0a212a5bd1f30a498395c0274" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.14", ] [[package]] @@ -953,6 +1023,15 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "miniz_oxide" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa679ff6578b1cddee93d7e82e263b94a575e0bfced07284eb0c037c1d2416a5" +dependencies = [ + "adler32", +] + [[package]] name = "more-asserts" version = "0.2.1" @@ -1055,6 +1134,19 @@ dependencies = [ "libc", ] +[[package]] +name = "object" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df4af347f5ac3d0e83e78c26be33cd10e8e874dcb68517a909ad802ba50a90b5" +dependencies = [ + "flate2", + "goblin 0.0.22", + "parity-wasm", + "scroll 0.9.2", + "uuid", +] + [[package]] name = "opaque-debug" version = "0.2.3" @@ -1080,6 +1172,12 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "parity-wasm" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20d7e522a7f994cc4ae32970b1ce0d99ecf91b8e1df080517a26faa6d2e2ee62" + [[package]] name = "paste" version = "0.1.6" @@ -1097,9 +1195,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4214c9e912ef61bf42b81ba9a47e8aad1b2ffaf739ab162bf96d1e011f54e6c5" dependencies = [ "proc-macro-hack", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.14", ] [[package]] @@ -1138,10 +1236,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "875077759af22fa20b610ad4471d8155b321c89c3f2785526c9839b099be4e0a" dependencies = [ "proc-macro-error-attr", - "proc-macro2", - "quote", + "proc-macro2 1.0.8", + "quote 1.0.2", "rustversion", - "syn", + "syn 1.0.14", ] [[package]] @@ -1150,10 +1248,10 @@ version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5717d9fa2664351a01ed73ba5ef6df09c01a521cb42cb65a061432a826f3c7a" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.8", + "quote 1.0.2", "rustversion", - "syn", + "syn 1.0.14", "syn-mid", ] @@ -1163,9 +1261,18 @@ version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.14", +] + +[[package]] +name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +dependencies = [ + "unicode-xid 0.1.0", ] [[package]] @@ -1174,7 +1281,7 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acb317c6ff86a4e579dfa00fc5e6cca91ecbb4e7eb2df0468805b674eb88548" dependencies = [ - "unicode-xid", + "unicode-xid 0.2.0", ] [[package]] @@ -1204,9 +1311,9 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4882d8237fd8c7373cc25cb802fe0dab9ff70830fd56f47ef6c7f3f287fcc057" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.14", ] [[package]] @@ -1215,10 +1322,10 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdf321cfab555f7411298733c86d21e5136f5ded13f5872fabf9de3337beecda" dependencies = [ - "proc-macro2", + "proc-macro2 1.0.8", "pyo3-derive-backend", - "quote", - "syn", + "quote 1.0.2", + "syn 1.0.14", ] [[package]] @@ -1239,13 +1346,22 @@ dependencies = [ "rand_core", ] +[[package]] +name = "quote" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +dependencies = [ + "proc-macro2 0.4.30", +] + [[package]] name = "quote" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" dependencies = [ - "proc-macro2", + "proc-macro2 1.0.8", ] [[package]] @@ -1410,12 +1526,9 @@ checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" [[package]] name = "rustc-hash" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7540fc8b0c49f096ee9c961cda096467dce8084bec6bdca2fc83895fd9b28cb8" -dependencies = [ - "byteorder", -] +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc_version" @@ -1432,9 +1545,9 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3bba175698996010c4f6dce5e7f173b6eb781fce25d2cfc45e27091ce0b79f6" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.14", ] [[package]] @@ -1449,13 +1562,34 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" +[[package]] +name = "scroll" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f84d114ef17fd144153d608fba7c446b0145d038985e7a8cc5d08bb0ce20383" +dependencies = [ + "rustc_version", + "scroll_derive 0.9.5", +] + [[package]] name = "scroll" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb2332cb595d33f7edd5700f4cbf94892e680c7f0ae56adab58a35190b66cb1" dependencies = [ - "scroll_derive", + "scroll_derive 0.10.1", +] + +[[package]] +name = "scroll_derive" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1aa96c45e7f5a91cb7fabe7b279f02fea7126239fc40b732316e8b6a2d0fcb" +dependencies = [ + "proc-macro2 0.4.30", + "quote 0.6.13", + "syn 0.15.44", ] [[package]] @@ -1464,9 +1598,9 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8584eea9b9ff42825b46faf46a8c24d2cff13ec152fa2a50df788b87c07ee28" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.14", ] [[package]] @@ -1499,16 +1633,16 @@ version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.14", ] [[package]] name = "serde_json" -version = "1.0.46" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b01d7f0288608a01dca632cf1df859df6fd6ffa885300fc275ce2ba6221953" +checksum = "15913895b61e0be854afd32fd4163fcd2a3df34142cf2cb961b310ce694cbf90" dependencies = [ "itoa", "ryu", @@ -1585,9 +1719,20 @@ checksum = "095064aa1f5b94d14e635d0a5684cf140c43ae40a0fd990708d38f5d669e5f64" dependencies = [ "heck", "proc-macro-error", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.14", +] + +[[package]] +name = "syn" +version = "0.15.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" +dependencies = [ + "proc-macro2 0.4.30", + "quote 0.6.13", + "unicode-xid 0.1.0", ] [[package]] @@ -1596,9 +1741,9 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f5" dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", + "proc-macro2 1.0.8", + "quote 1.0.2", + "unicode-xid 0.2.0", ] [[package]] @@ -1607,9 +1752,9 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.14", ] [[package]] @@ -1618,10 +1763,21 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" dependencies = [ - "proc-macro2", - "quote", - "syn", - "unicode-xid", + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.14", + "unicode-xid 0.2.0", +] + +[[package]] +name = "target-lexicon" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b0ab4982b8945c35cc1c46a83a9094c414f6828a099ce5dcaa8ee2b04642dcb" +dependencies = [ + "failure", + "failure_derive", + "serde_json", ] [[package]] @@ -1661,7 +1817,7 @@ dependencies = [ "cfg-if", "os_pipe", "pretty_env_logger", - "target-lexicon", + "target-lexicon 0.10.0", "tempfile", "wasi-common", "wasmtime", @@ -1693,9 +1849,9 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57e4d2e50ca050ed44fb58309bdce3efa79948f84f9993ad1978de5eebdce5a7" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.14", ] [[package]] @@ -1760,6 +1916,12 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" + [[package]] name = "unicode-xid" version = "0.2.0" @@ -1781,6 +1943,12 @@ dependencies = [ "traitobject", ] +[[package]] +name = "uuid" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" + [[package]] name = "vec_map" version = "0.8.1" @@ -1820,9 +1988,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8757b0da38353d55a9687f4dee68a8f441f980dd36e16ab07d6e6c673f505f76" dependencies = [ "heck", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.14", ] [[package]] @@ -1905,11 +2073,12 @@ dependencies = [ "rayon", "region", "rustc-demangle", - "target-lexicon", + "target-lexicon 0.10.0", "wasi-common", "wasmparser 0.51.1", "wasmtime-environ", "wasmtime-jit", + "wasmtime-profiling", "wasmtime-runtime", "wat", "winapi", @@ -1934,7 +2103,7 @@ dependencies = [ "pretty_env_logger", "rayon", "structopt", - "target-lexicon", + "target-lexicon 0.10.0", "tempfile", "test-programs", "wasi-common", @@ -1945,6 +2114,7 @@ dependencies = [ "wasmtime-interface-types", "wasmtime-jit", "wasmtime-obj", + "wasmtime-profiling", "wasmtime-runtime", "wasmtime-wasi", "wasmtime-wast", @@ -1959,7 +2129,7 @@ dependencies = [ "faerie", "gimli 0.19.0", "more-asserts", - "target-lexicon", + "target-lexicon 0.10.0", "thiserror", "wasmparser 0.51.1", "wasmtime-environ", @@ -1990,7 +2160,7 @@ dependencies = [ "rayon", "serde", "sha2", - "target-lexicon", + "target-lexicon 0.10.0", "tempfile", "thiserror", "toml", @@ -2052,11 +2222,12 @@ dependencies = [ "cranelift-wasm", "more-asserts", "region", - "target-lexicon", + "target-lexicon 0.10.0", "thiserror", "wasmparser 0.51.1", "wasmtime-debug", "wasmtime-environ", + "wasmtime-profiling", "wasmtime-runtime", "winapi", ] @@ -2071,6 +2242,20 @@ dependencies = [ "wasmtime-environ", ] +[[package]] +name = "wasmtime-profiling" +version = "0.1.0" +dependencies = [ + "gimli 0.19.0", + "goblin 0.0.24", + "lazy_static", + "libc", + "object", + "scroll 0.9.2", + "serde", + "target-lexicon 0.4.0", +] + [[package]] name = "wasmtime-py" version = "0.9.0" @@ -2078,7 +2263,7 @@ dependencies = [ "anyhow", "pyo3", "region", - "target-lexicon", + "target-lexicon 0.10.0", "wasmparser 0.51.1", "wasmtime", "wasmtime-interface-types", @@ -2099,6 +2284,7 @@ dependencies = [ "region", "thiserror", "wasmtime-environ", + "wasmtime-profiling", "winapi", ] @@ -2117,9 +2303,9 @@ dependencies = [ name = "wasmtime-rust-macro" version = "0.9.0" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.14", ] [[package]] @@ -2184,8 +2370,8 @@ name = "wig" version = "0.9.2" dependencies = [ "heck", - "proc-macro2", - "quote", + "proc-macro2 1.0.8", + "quote 1.0.2", "witx", ] diff --git a/Cargo.toml b/Cargo.toml index 8bceb9ba7518..45467fd5867b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ wasmtime-environ = { path = "crates/environ" } wasmtime-interface-types = { path = "crates/interface-types" } wasmtime-jit = { path = "crates/jit" } wasmtime-obj = { path = "crates/obj" } +wasmtime-profiling = { path = "crates/profiling" } wasmtime-wast = { path = "crates/wast" } wasmtime-wasi = { path = "crates/wasi" } wasi-common = { path = "crates/wasi-common" } @@ -74,6 +75,7 @@ lightbeam = [ "wasmtime-wast/lightbeam", "wasmtime/lightbeam", ] +profiling = ["wasmtime-jit/profiling", "wasmtime/profiling", "wasmtime-profiling/profiling"] test_programs = ["test-programs/test_programs"] [badges] diff --git a/crates/api/Cargo.toml b/crates/api/Cargo.toml index 599d5ea9b86c..722abb9972ca 100644 --- a/crates/api/Cargo.toml +++ b/crates/api/Cargo.toml @@ -12,6 +12,7 @@ edition = "2018" wasmtime-runtime = { path = "../runtime", version = "0.9.0" } wasmtime-environ = { path = "../environ", version = "0.9.0" } wasmtime-jit = { path = "../jit", version = "0.9.0" } +wasmtime-profiling = { path = "../profiling" } wasmparser = "0.51.1" target-lexicon = { version = "0.10.0", default-features = false } anyhow = "1.0.19" @@ -44,3 +45,4 @@ default = ['wat'] # to cranelift. Requires Nightly Rust currently, and this is not enabled by # default. lightbeam = ["wasmtime-jit/lightbeam"] +profiling = [] diff --git a/crates/api/src/module.rs b/crates/api/src/module.rs index c8f159e551c1..dd463d2fc55d 100644 --- a/crates/api/src/module.rs +++ b/crates/api/src/module.rs @@ -253,6 +253,7 @@ impl Module { &mut store.compiler_mut(), binary, store.engine().config().debug_info, + store.engine().config().profiler.as_ref(), )?; let names = Arc::new(Names { diff --git a/crates/api/src/runtime.rs b/crates/api/src/runtime.rs index 705491807991..09dea30bd777 100644 --- a/crates/api/src/runtime.rs +++ b/crates/api/src/runtime.rs @@ -3,11 +3,12 @@ use std::cell::RefCell; use std::fmt; use std::path::Path; use std::rc::Rc; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use wasmparser::{OperatorValidatorConfig, ValidatingParserConfig}; use wasmtime_environ::settings::{self, Configurable}; use wasmtime_environ::CacheConfig; use wasmtime_jit::{native, CompilationStrategy, Compiler}; +use wasmtime_profiling::{JitDumpAgent, ProfilingAgent, ProfilingStrategy}; // Runtime Environment @@ -25,6 +26,7 @@ pub struct Config { pub(crate) debug_info: bool, pub(crate) strategy: CompilationStrategy, pub(crate) cache_config: CacheConfig, + pub(crate) profiler: Option>>>, } impl Config { @@ -58,6 +60,7 @@ impl Config { flags, strategy: CompilationStrategy::Auto, cache_config: CacheConfig::new_cache_disabled(), + profiler: None, } } @@ -212,6 +215,20 @@ impl Config { Ok(self) } + /// Creates a default profiler based on the profiling strategy choosen + /// + /// Profiler creation calls the type's default initializer where the purpose is + /// really just to put in place the type used for profiling. + pub fn profiler(&mut self, profile: ProfilingStrategy) -> Result<&mut Self> { + match profile { + ProfilingStrategy::JitDumpProfiler => { + self.profiler = { Some(Arc::new(Mutex::new(Box::new(JitDumpAgent::default())))) } + } + _ => self.profiler = { None }, + }; + Ok(self) + } + /// Configures whether the debug verifier of Cranelift is enabled or not. /// /// When Cranelift is used as a code generation backend this will configure diff --git a/crates/jit/Cargo.toml b/crates/jit/Cargo.toml index 2212663384c5..2e426b1badc8 100644 --- a/crates/jit/Cargo.toml +++ b/crates/jit/Cargo.toml @@ -19,6 +19,7 @@ cranelift-frontend = "0.58.0" wasmtime-environ = { path = "../environ", version = "0.9.0" } wasmtime-runtime = { path = "../runtime", version = "0.9.0" } wasmtime-debug = { path = "../debug", version = "0.9.0" } +wasmtime-profiling = { path = "../profiling" } region = "2.0.0" thiserror = "1.0.4" target-lexicon = { version = "0.10.0", default-features = false } @@ -32,6 +33,7 @@ winapi = { version = "0.3.7", features = ["winnt", "impl-default"] } [features] lightbeam = ["wasmtime-environ/lightbeam"] +profiling = ["wasmtime-profiling/profiling"] [badges] maintenance = { status = "actively-developed" } diff --git a/crates/jit/src/code_memory.rs b/crates/jit/src/code_memory.rs index 2eb4806364a4..4670f40dfc67 100644 --- a/crates/jit/src/code_memory.rs +++ b/crates/jit/src/code_memory.rs @@ -5,6 +5,7 @@ use region; use std::mem::ManuallyDrop; use std::{cmp, mem}; use wasmtime_environ::{Compilation, CompiledFunction}; +use wasmtime_profiling::ProfilingAgent; use wasmtime_runtime::{Mmap, VMFunctionBody}; struct CodeMemoryEntry { @@ -230,4 +231,22 @@ impl CodeMemory { Ok(()) } + + /// Calls the module_load for a given ProfilerAgent. Includes + /// all memory address and length for the given module. + /// TODO: Properly handle the possibilities of multiple mmapped regions + /// which may, amongst other things, influence being more specific about + /// the module name. + pub fn profiler_module_load( + &mut self, + profiler: &mut Box, + module_name: &str, + dbg_image: Option<&[u8]>, + ) -> () { + for CodeMemoryEntry { mmap: m, table: _t } in &mut self.entries { + if m.len() > 0 { + profiler.module_load(module_name, m.as_ptr(), m.len(), dbg_image); + } + } + } } diff --git a/crates/jit/src/compiler.rs b/crates/jit/src/compiler.rs index 21a274eb23f6..34f55ae57142 100644 --- a/crates/jit/src/compiler.rs +++ b/crates/jit/src/compiler.rs @@ -20,6 +20,7 @@ use wasmtime_environ::{ Compiler as _C, FunctionBodyData, Module, ModuleMemoryOffset, ModuleVmctxInfo, Relocations, Traps, Tunables, VMOffsets, }; +use wasmtime_profiling::ProfilingAgent; use wasmtime_runtime::{ InstantiationError, SignatureRegistry, TrapRegistration, TrapRegistry, VMFunctionBody, }; @@ -240,6 +241,16 @@ impl Compiler { self.code_memory.publish(); } + pub(crate) fn profiler_module_load( + &mut self, + profiler: &mut Box, + module_name: &str, + dbg_image: Option<&[u8]>, + ) -> () { + self.code_memory + .profiler_module_load(profiler, module_name, dbg_image); + } + /// Shared signature registry. pub fn signatures(&self) -> &SignatureRegistry { &self.signatures diff --git a/crates/jit/src/instantiate.rs b/crates/jit/src/instantiate.rs index f6b33e41ffe3..66d24e32a43f 100644 --- a/crates/jit/src/instantiate.rs +++ b/crates/jit/src/instantiate.rs @@ -9,7 +9,7 @@ use crate::link::link_module; use crate::resolver::Resolver; use std::io::Write; use std::rc::Rc; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use thiserror::Error; use wasmtime_debug::read_debuginfo; use wasmtime_environ::entity::{BoxedSlice, PrimaryMap}; @@ -17,6 +17,8 @@ use wasmtime_environ::wasm::{DefinedFuncIndex, SignatureIndex}; use wasmtime_environ::{ CompileError, DataInitializer, DataInitializerLocation, Module, ModuleEnvironment, }; + +use wasmtime_profiling::ProfilingAgent; use wasmtime_runtime::{ GdbJitImageRegistration, InstanceHandle, InstantiationError, TrapRegistration, VMFunctionBody, VMSharedSignatureIndex, @@ -61,6 +63,7 @@ impl<'data> RawCompiledModule<'data> { compiler: &mut Compiler, data: &'data [u8], debug_info: bool, + profiler: Option<&Arc>>>, ) -> Result { let environ = ModuleEnvironment::new(compiler.frontend_config(), compiler.tunables()); @@ -103,6 +106,23 @@ impl<'data> RawCompiledModule<'data> { // Make all code compiled thus far executable. compiler.publish_compiled_code(); + // Initialize profiler and load the wasm module + let _: Option> = match profiler { + Some(_) => { + let region_name = String::from("wasm_module"); + let mut profiler_new = profiler.unwrap().lock().unwrap().clone(); + profiler_new.init().ok(); + match &dbg_image { + Some(dbg) => { + compiler.profiler_module_load(&mut profiler_new, ®ion_name, Some(&dbg)) + } + _ => compiler.profiler_module_load(&mut profiler_new, ®ion_name, None), + }; + Some(profiler_new) + } + _ => None, + }; + let dbg_jit_registration = if let Some(img) = dbg_image { let mut bytes = Vec::new(); bytes.write_all(&img).expect("all written"); @@ -139,8 +159,9 @@ impl CompiledModule { compiler: &mut Compiler, data: &'data [u8], debug_info: bool, + profiler: Option<&Arc>>>, ) -> Result { - let raw = RawCompiledModule::<'data>::new(compiler, data, debug_info)?; + let raw = RawCompiledModule::<'data>::new(compiler, data, debug_info, profiler)?; Ok(Self::from_parts( raw.module, @@ -246,7 +267,7 @@ impl OwnedDataInitializer { /// Create a new wasm instance by compiling the wasm module in `data` and instatiating it. /// -/// This is equivalent to createing a `CompiledModule` and calling `instantiate()` on it, +/// This is equivalent to creating a `CompiledModule` and calling `instantiate()` on it, /// but avoids creating an intermediate copy of the data initializers. /// /// # Unsafety @@ -258,7 +279,9 @@ pub unsafe fn instantiate( data: &[u8], resolver: &mut dyn Resolver, debug_info: bool, + profiler: Option<&Arc>>>, ) -> Result { - let instance = CompiledModule::new(compiler, data, debug_info)?.instantiate(resolver)?; + let instance = + CompiledModule::new(compiler, data, debug_info, profiler)?.instantiate(resolver)?; Ok(instance) } diff --git a/crates/profiling/Cargo.toml b/crates/profiling/Cargo.toml new file mode 100644 index 000000000000..1d621e657efe --- /dev/null +++ b/crates/profiling/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "wasmtime-profiling" +version = "0.1.0" +authors = ["The Wasmtime Project Developers"] +description = "Runtime library support for Wasmtime" +license = "Apache-2.0 WITH LLVM-exception" +categories = ["wasm"] +keywords = ["webassembly", "wasm"] +repository = "https://github.com/bytecodealliance/wasmtime" +readme = "README.md" +edition = "2018" + +[dependencies] +libc = { version = "0.2.60", default-features = false } +goblin = "0.0.24" +serde = { version = "1.0.99", features = ["derive"] } +scroll = "0.9.2" +gimli = "0.19.0" +object = "0.12.0" +target-lexicon = "0.4.0" +lazy_static = "1.4" + +[badges] +maintenance = { status = "actively-developed" } + +[features] +profiling = [] diff --git a/crates/profiling/src/jitdump.rs b/crates/profiling/src/jitdump.rs new file mode 100644 index 000000000000..fb5c79aaef5a --- /dev/null +++ b/crates/profiling/src/jitdump.rs @@ -0,0 +1,720 @@ +//! Support for jitdump files which can be used by perf for profiling jitted code. +//! Spec definitions for the output format is as described here: +//! https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/perf/Documentation/jitdump-specification.txt +//! +//! Usage Example: +//! Record +//! sudo perf record -k 1 -e instructions:u target/debug/wasmtime -g --jitdump test.wasm +//! Combine +//! sudo perf inject -v -j -i perf.data -o perf.jit.data +//! Report +//! sudo perf report -i perf.jit.data -F+period,srcline +//! Note: For descriptive results, the WASM file being executed should contain dwarf debug data +use libc::c_int; +#[cfg(not(target_os = "windows"))] +use libc::{c_void, clock_gettime, mmap, open, sysconf}; +use object::Object; +use scroll::{IOwrite, SizeWith, NATIVE}; +use serde::{Deserialize, Serialize}; +#[cfg(not(target_os = "windows"))] +use std::ffi::CString; +use std::fmt::Debug; +use std::fs::File; +use std::io::Write; +#[cfg(not(target_os = "windows"))] +use std::os::unix::io::FromRawFd; +use std::{borrow, mem, process}; +use target_lexicon::Architecture; + +#[cfg(target_pointer_width = "64")] +use goblin::elf64 as elf; + +#[cfg(target_pointer_width = "32")] +use goblin::elf32 as elf; + +/// Defines jitdump record types +#[repr(u32)] +pub enum RecordId { + /// Value 0: JIT_CODE_LOAD: record describing a jitted function + JitCodeLoad = 0, + /// Value 1: JIT_CODE_MOVE: record describing an already jitted function which is moved + _JitCodeMove = 1, + /// Value 2: JIT_CODE_DEBUG_INFO: record describing the debug information for a jitted function + JitCodeDebugInfo = 2, + /// Value 3: JIT_CODE_CLOSE: record marking the end of the jit runtime (optional) + _JitCodeClose = 3, + /// Value 4: JIT_CODE_UNWINDING_INFO: record describing a function unwinding information + _JitCodeUnwindingInfo = 4, +} + +/// Each record starts with this fixed size record header which describes the record that follows +#[derive(Serialize, Deserialize, Debug, Default, Clone, Copy, IOwrite, SizeWith)] +#[repr(C)] +pub struct RecordHeader { + /// uint32_t id: a value identifying the record type (see below) + id: u32, + /// uint32_t total_size: the size in bytes of the record including the header. + record_size: u32, + /// uint64_t timestamp: a timestamp of when the record was created. + timestamp: u64, +} + +/// The CodeLoadRecord is used for describing jitted functions +#[derive(Serialize, Deserialize, Debug, Default, Clone, Copy, IOwrite, SizeWith)] +#[repr(C)] +pub struct CodeLoadRecord { + /// Fixed sized header that describes this record + header: RecordHeader, + /// uint32_t pid: OS process id of the runtime generating the jitted code + pid: u32, + /// uint32_t tid: OS thread identification of the runtime thread generating the jitted code + tid: u32, + /// uint64_t vma: virtual address of jitted code start + virtual_address: u64, + /// uint64_t code_addr: code start address for the jitted code. By default vma = code_addr + address: u64, + /// uint64_t code_size: size in bytes of the generated jitted code + size: u64, + /// uint64_t code_index: unique identifier for the jitted code (see below) + index: u64, +} + +/// Describes source line information for a jitted function +#[derive(Serialize, Deserialize, Debug, Default)] +#[repr(C)] +pub struct DebugEntry { + /// uint64_t code_addr: address of function for which the debug information is generated + address: u64, + /// uint32_t line: source file line number (starting at 1) + line: u32, + /// uint32_t discrim: column discriminator, 0 is default + discriminator: u32, + /// char name[n]: source file name in ASCII, including null termination + filename: String, +} + +/// Describes debug information for a jitted function. An array of debug entries are +/// appended to this record during writting. Note, this record must preceed the code +/// load record that describes the same jitted function. +#[derive(Serialize, Deserialize, Debug, Default, Clone, Copy, IOwrite, SizeWith)] +#[repr(C)] +pub struct DebugInfoRecord { + /// Fixed sized header that describes this record + header: RecordHeader, + /// uint64_t code_addr: address of function for which the debug information is generated + address: u64, + /// uint64_t nr_entry: number of debug entries for the function appended to this record + count: u64, +} + +/// Fixed-sized header for each jitdump file +#[derive(Serialize, Deserialize, Debug, Default, IOwrite, SizeWith)] +#[repr(C)] +pub struct FileHeader { + /// uint32_t magic: a magic number tagging the file type. The value is 4-byte long and represents the + /// string "JiTD" in ASCII form. It is 0x4A695444 or 0x4454694a depending on the endianness. The field can + /// be used to detect the endianness of the file + magic: u32, + /// uint32_t version: a 4-byte value representing the format version. It is currently set to 2 + version: u32, + /// uint32_t total_size: size in bytes of file header + size: u32, + /// uint32_t elf_mach: ELF architecture encoding (ELF e_machine value as specified in /usr/include/elf.h) + e_machine: u32, + /// uint32_t pad1: padding. Reserved for future use + pad1: u32, + /// uint32_t pid: JIT runtime process identification (OS specific) + pid: u32, + /// uint64_t timestamp: timestamp of when the file was created + timestamp: u64, + /// uint64_t flags: a bitmask of flags + flags: u64, +} + +/// Interface for driving the creation of jitdump files +#[derive(Debug, Default)] +pub struct JitDumpAgent { + /// File instance for the jit dump file + pub jitdump_file: Option, + /// Unique identifier for jitted code + pub code_index: u64, + /// Flag for experimenting with dumping code load record + /// after each function (true) or after each module. This + /// flag is currently set to true. + pub dump_funcs: bool, +} + +impl JitDumpAgent { + /// Intialize a JitDumpAgent and write out the header + pub fn init(&mut self) -> Result<(), Box> { + #[cfg(target_os = "windows")] + return Err(Error::NulError); + #[cfg(not(target_os = "windows"))] + { + let filename = format!("./jit-{}.dump", process::id()); + let mut jitdump_file; + unsafe { + let filename_c = CString::new(filename)?; + let fd = open( + filename_c.as_ptr(), + libc::O_CREAT | libc::O_TRUNC | libc::O_RDWR, + 0666, + ); + let pgsz = sysconf(libc::_SC_PAGESIZE) as usize; + mmap( + 0 as *mut c_void, + pgsz, + libc::PROT_EXEC | libc::PROT_READ, + libc::MAP_PRIVATE, + fd, + 0, + ); + jitdump_file = File::from_raw_fd(fd); + } + JitDumpAgent::write_file_header(&mut jitdump_file)?; + self.jitdump_file = Some(jitdump_file); + self.code_index = 0; + self.dump_funcs = true; + Ok(()) + } + } + + /// Returns timestamp from a single source + #[allow(unused_variables)] + fn get_time_stamp(timestamp: &mut u64) -> c_int { + #[cfg(not(target_os = "windows"))] + { + unsafe { + let mut ts = mem::MaybeUninit::zeroed().assume_init(); + clock_gettime(libc::CLOCK_MONOTONIC, &mut ts); + // TODO: What does it mean for either sec or nsec to be negative? + *timestamp = (ts.tv_sec * 1000000000 + ts.tv_nsec) as u64; + } + } + return 0; + } + + /// Returns the ELF machine architecture. + #[allow(dead_code)] + fn get_e_machine() -> u32 { + match target_lexicon::HOST.architecture { + Architecture::X86_64 => elf::header::EM_X86_64 as u32, + Architecture::I686 => elf::header::EM_386 as u32, + Architecture::Arm => elf::header::EM_ARM as u32, + Architecture::Armv4t => elf::header::EM_ARM as u32, + Architecture::Armv5te => elf::header::EM_ARM as u32, + Architecture::Armv7 => elf::header::EM_ARM as u32, + Architecture::Armv7s => elf::header::EM_ARM as u32, + Architecture::Aarch64 => elf::header::EM_AARCH64 as u32, + _ => unimplemented!("unrecognized architecture"), + } + } + + #[allow(dead_code)] + fn write_file_header(file: &mut File) -> Result<(), JitDumpError> { + let mut header: FileHeader = Default::default(); + let mut timestamp: u64 = 0; + JitDumpAgent::get_time_stamp(&mut timestamp); + header.timestamp = timestamp; + + let e_machine = JitDumpAgent::get_e_machine(); + if e_machine != elf::header::EM_NONE as u32 { + header.e_machine = e_machine; + } + + if cfg!(target_endian = "little") { + header.magic = 0x4A695444 + } else { + header.magic = 0x4454694a + } + header.version = 1; + header.size = mem::size_of::() as u32; + header.pad1 = 0; + header.pid = process::id(); + header.flags = 0; + + file.iowrite_with(header, NATIVE)?; + Ok(()) + } + + fn write_code_load_record( + &mut self, + record_name: &str, + cl_record: CodeLoadRecord, + code_buffer: &[u8], + ) -> Result<(), Error> { + let mut jitdump_file = self.jitdump_file.as_ref().unwrap(); + jitdump_file.iowrite_with(cl_record, NATIVE)?; + jitdump_file.write_all(record_name.as_bytes())?; + jitdump_file.write_all(b"\0")?; + jitdump_file.write_all(code_buffer)?; + Ok(()) + } + + /// Write DebugInfoRecord to open jit dump file. + /// Must be written before the corresponding CodeLoadRecord. + fn write_debug_info_record(&mut self, dir_record: DebugInfoRecord) -> Result<(), Error> { + self.jitdump_file + .as_ref() + .unwrap() + .iowrite_with(dir_record, NATIVE)?; + Ok(()) + } + + /// Write DebugInfoRecord to open jit dump file. + /// Must be written before the corresponding CodeLoadRecord. + fn write_debug_info_entries(&mut self, die_entries: Vec) -> Result<(), Error> { + for entry in die_entries.iter() { + let mut jitdump_file = self.jitdump_file.as_ref().unwrap(); + jitdump_file.iowrite_with(entry.address, NATIVE)?; + jitdump_file.iowrite_with(entry.line, NATIVE)?; + jitdump_file.iowrite_with(entry.discriminator, NATIVE)?; + jitdump_file.write_all(entry.filename.as_bytes())?; + jitdump_file.write_all(b"\0")?; + } + Ok(()) + } + + /// Sent when a method is compiled and loaded into memory by the VM. + pub fn module_load( + &mut self, + module_name: &str, + addr: *const u8, + len: usize, + dbg_image: Option<&[u8]>, + ) -> () { + let pid = process::id(); + let tid = pid; // ThreadId does appear to track underlying thread. Using PID. + + if let Some(img) = &dbg_image { + let _ = self.dump_from_debug_image(img, module_name, addr, len, pid, tid); + } else { + let mut timestamp: u64 = 0; + JitDumpAgent::get_time_stamp(&mut timestamp); + self.dump_code_load_record(module_name, addr, len, timestamp, pid, tid); + } + } + + fn dump_code_load_record( + &mut self, + method_name: &str, + addr: *const u8, + len: usize, + timestamp: u64, + pid: u32, + tid: u32, + ) -> () { + let name_len = method_name.len() + 1; + let size_limit = mem::size_of::(); + + let rh = RecordHeader { + id: RecordId::JitCodeLoad as u32, + record_size: size_limit as u32 + name_len as u32 + len as u32, + timestamp: timestamp, + }; + + let clr = CodeLoadRecord { + header: rh, + pid: pid, + tid: tid, + virtual_address: addr as u64, + address: addr as u64, + size: len as u64, + index: self.code_index, + }; + self.code_index += 1; + + unsafe { + let code_buffer: &[u8] = std::slice::from_raw_parts(addr, len); + let _ = self.write_code_load_record(method_name, clr, code_buffer); + } + } + + /// Attempts to dump debuginfo data structures, adding method and line level + /// for the jitted function. + pub fn dump_from_debug_image( + &mut self, + dbg_image: &[u8], + module_name: &str, + addr: *const u8, + len: usize, + pid: u32, + tid: u32, + ) -> Result<(), Error> { + let file = object::File::parse(&dbg_image).unwrap(); + let endian = if file.is_little_endian() { + gimli::RunTimeEndian::Little + } else { + gimli::RunTimeEndian::Big + }; + + let load_section = |id: gimli::SectionId| -> Result, Error> { + Ok(file + .section_data_by_name(id.name()) + .unwrap_or(borrow::Cow::Borrowed(&[][..]))) + }; + + let load_section_sup = |_| Ok(borrow::Cow::Borrowed(&[][..])); + let dwarf_cow = gimli::Dwarf::load(&load_section, &load_section_sup)?; + let borrow_section: &dyn for<'a> Fn( + &'a borrow::Cow<[u8]>, + ) + -> gimli::EndianSlice<'a, gimli::RunTimeEndian> = + &|section| gimli::EndianSlice::new(&*section, endian); + + let dwarf = dwarf_cow.borrow(&borrow_section); + + let mut iter = dwarf.units(); + while let Some(header) = iter.next()? { + let unit = match dwarf.unit(header) { + Ok(unit) => unit, + Err(_err) => { + return Ok(()); + } + }; + self.dump_entries(unit, &dwarf, module_name, addr, len, pid, tid)?; + // TODO: Temp exit to avoid duplicate addresses being covered by only + // processing the top unit + break; + } + if !self.dump_funcs { + let mut timestamp: u64 = 0; + JitDumpAgent::get_time_stamp(&mut timestamp); + self.dump_code_load_record(module_name, addr, len, timestamp, pid, tid); + } + Ok(()) + } + + fn dump_entries( + &mut self, + unit: gimli::Unit, + dwarf: &gimli::Dwarf, + module_name: &str, + addr: *const u8, + len: usize, + pid: u32, + tid: u32, + ) -> Result<(), Error> { + let mut depth = 0; + let mut entries = unit.entries(); + while let Some((delta_depth, entry)) = entries.next_dfs()? { + if self.dump_funcs { + let record_header = RecordHeader { + id: RecordId::JitCodeLoad as u32, + record_size: 0, + timestamp: 0, + }; + + let mut clr = CodeLoadRecord { + header: record_header, + pid: pid, + tid: tid, + virtual_address: 0, + address: 0, + size: 0, + index: 0, + }; + let mut clr_name: String = String::from(module_name); + let mut get_debug_entry = false; + depth += delta_depth; + assert!(depth >= 0); + + if entry.tag() == gimli::constants::DW_TAG_subprogram { + get_debug_entry = true; + + let mut attrs = entry.attrs(); + while let Some(attr) = attrs.next()? { + if let Some(n) = attr.name().static_string() { + if n == "DW_AT_low_pc" { + clr.address = match attr.value() { + gimli::AttributeValue::Addr(address) => address, + _ => 0, + }; + clr.virtual_address = clr.address; + } else if n == "DW_AT_high_pc" { + clr.size = match attr.value() { + gimli::AttributeValue::Udata(data) => data, + _ => 0, + }; + } else if n == "DW_AT_name" { + clr_name = match attr.value() { + gimli::AttributeValue::DebugStrRef(offset) => { + if let Ok(s) = dwarf.debug_str.get_str(offset) { + clr_name.push_str("::"); + clr_name.push_str(&s.to_string_lossy()?); + clr_name + } else { + clr_name.push_str("::"); + clr_name.push_str("?"); + clr_name + } + } + _ => { + clr_name.push_str("??"); + clr_name + } + }; + } + } + } + } + if get_debug_entry { + // TODO: Temp check to make sure well only formed data is processed. + if clr.address == 0 { + continue; + } + // TODO: Temp check to make sure well only formed data is processed. + if clr_name == "?" { + continue; + } + if clr.address == 0 || clr.size == 0 { + clr.address = addr as u64; + clr.virtual_address = addr as u64; + clr.size = len as u64; + } + clr.header.record_size = mem::size_of::() as u32 + + (clr_name.len() + 1) as u32 + + clr.size as u32; + clr.index = self.code_index; + self.code_index += 1; + self.dump_debug_info(&unit, &dwarf, clr.address, clr.size, None)?; + + let mut timestamp: u64 = 0; + JitDumpAgent::get_time_stamp(&mut timestamp); + clr.header.timestamp = timestamp; + + unsafe { + let code_buffer: &[u8] = + std::slice::from_raw_parts(clr.address as *const u8, clr.size as usize); + let _ = self.write_code_load_record(&clr_name, clr, code_buffer); + } + } + } else { + let mut func_name: String = String::from("?"); + let mut func_addr = 0; + let mut func_size = 0; + + let mut get_debug_entry = false; + depth += delta_depth; + assert!(depth >= 0); + if entry.tag() == gimli::constants::DW_TAG_subprogram { + get_debug_entry = true; + + let mut attrs = entry.attrs(); + while let Some(attr) = attrs.next()? { + if let Some(n) = attr.name().static_string() { + if n == "DW_AT_low_pc" { + func_addr = match attr.value() { + gimli::AttributeValue::Addr(address) => address, + _ => 0, + }; + } else if n == "DW_AT_high_pc" { + func_size = match attr.value() { + gimli::AttributeValue::Udata(data) => data, + _ => 0, + }; + } else if n == "DW_AT_name" { + func_name = match attr.value() { + gimli::AttributeValue::DebugStrRef(offset) => { + if let Ok(s) = dwarf.debug_str.get_str(offset) { + func_name.clear(); + func_name.push_str(&s.to_string_lossy()?); + func_name + } else { + func_name.push_str("?"); + func_name + } + } + _ => { + func_name.push_str("??"); + func_name + } + }; + } + } + } + } + if get_debug_entry { + // TODO: Temp check to make sure well only formed data is processed. + if func_addr == 0 { + continue; + } + // TODO: Temp check to make sure well only formed data is processed. + if func_name == "?" { + continue; + } + self.dump_debug_info( + &unit, + &dwarf, + func_addr, + func_size, + Some(func_name.as_str()), + )?; + } + } + } + Ok(()) + } + + fn dump_debug_info( + &mut self, + unit: &gimli::Unit, + dwarf: &gimli::Dwarf, + address: u64, + size: u64, + file_suffix: Option<&str>, + ) -> Result<(), Error> { + let mut timestamp: u64 = 0; + JitDumpAgent::get_time_stamp(&mut timestamp); + if let Some(program) = unit.line_program.clone() { + let mut debug_info_record = DebugInfoRecord { + header: RecordHeader { + id: RecordId::JitCodeDebugInfo as u32, + record_size: 0, + timestamp: timestamp, + }, + address: address, + count: 0, + }; + + let mut debug_entries = Vec::new(); + let mut debug_entries_total_filenames_len = 0; + let mut rows = program.rows(); + while let Some((header, row)) = rows.next_row()? { + let row_file_index = row.file_index() - 1; + let myfile = dwarf + .attr_string( + &unit, + header.file_names()[row_file_index as usize].path_name(), + ) + .unwrap(); + let filename = myfile.to_string_lossy()?; + let line = row.line().unwrap_or(0); + let column = match row.column() { + gimli::ColumnType::Column(column) => column, + gimli::ColumnType::LeftEdge => 0, + }; + + if (row.address() < address) || (row.address() > (address + size)) { + continue; + } + let mut debug_entry = DebugEntry { + address: row.address(), + line: line as u32, + discriminator: column as u32, + filename: filename.to_string(), + }; + + if let Some(suffix) = file_suffix { + debug_entry.filename.push_str("::"); + debug_entry.filename.push_str(suffix); + } + + debug_entries_total_filenames_len += debug_entry.filename.len() + 1; + debug_entries.push(debug_entry); + } + + debug_info_record.count = debug_entries.len() as u64; + + let debug_entries_size = (debug_info_record.count + * (mem::size_of::() as u64 - mem::size_of::() as u64)) + + debug_entries_total_filenames_len as u64; + debug_info_record.header.record_size = + mem::size_of::() as u32 + debug_entries_size as u32; + + let _ = self.write_debug_info_record(debug_info_record); + let _ = self.write_debug_info_entries(debug_entries); + } + Ok(()) + } +} + +use crate::ProfilingAgent; +impl ProfilingAgent for JitDumpAgent { + fn init(&mut self) -> Result<(), Box> { + JitDumpAgent::init(self) + } + + fn module_load( + &mut self, + module_name: &str, + addr: *const u8, + len: usize, + dbg_image: Option<&[u8]>, + ) -> () { + JitDumpAgent::module_load(self, module_name, addr, len, dbg_image); + } + + fn box_clone(&self) -> Box { + Box::new((*self).clone()) + } +} + +impl Clone for JitDumpAgent { + fn clone(&self) -> Self { + Self { + jitdump_file: match self.jitdump_file.as_ref() { + Some(file) => Some(file.try_clone().unwrap()), + None => None, + }, + code_index: self.code_index, + dump_funcs: self.dump_funcs, + } + } +} + +#[derive(Debug)] +struct JitDumpError; + +impl std::fmt::Display for JitDumpError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "A profiler agent is not supported by this build") + } +} + +// This is important for other errors to wrap this one. +impl std::error::Error for JitDumpError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + // Generic error, underlying cause isn't tracked. + None + } +} + +impl From for JitDumpError { + fn from(_err: std::io::Error) -> Self { + //Error::IOError + JitDumpError + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Error { + GimliError(gimli::Error), + IOError, + NulError, +} + +impl From for Error { + fn from(err: gimli::Error) -> Self { + Error::GimliError(err) + } +} + +impl From for Error { + fn from(_err: std::io::Error) -> Self { + Error::IOError + } +} + +impl From for Error { + fn from(_err: std::ffi::NulError) -> Self { + Error::NulError + } +} + +trait Reader: gimli::Reader + Send + Sync {} + +impl<'input, Endian> Reader for gimli::EndianSlice<'input, Endian> where + Endian: gimli::Endianity + Send + Sync +{ +} diff --git a/crates/profiling/src/lib.rs b/crates/profiling/src/lib.rs new file mode 100644 index 000000000000..a7c5181219d1 --- /dev/null +++ b/crates/profiling/src/lib.rs @@ -0,0 +1,84 @@ +use std::error::Error; +use std::fmt; + +#[cfg(feature = "profiling")] +mod jitdump; + +#[cfg(feature = "profiling")] +pub use crate::jitdump::JitDumpAgent; + +#[cfg(not(feature = "profiling"))] +pub type JitDumpAgent = NullProfilerAgent; + +/// Select which profiling technique to use +#[derive(Debug, Clone, Copy)] +pub enum ProfilingStrategy { + /// No profiler support + NullProfiler, + + /// Collect profile for jitdump file format + JitDumpProfiler, +} + +/// Common interface for profiling tools. +pub trait ProfilingAgent { + /// Initialize the profiler + fn init(&mut self) -> Result<(), Box>; + + /// Support for conditional profiling build. + fn module_load( + &mut self, + module_name: &str, + addr: *const u8, + len: usize, + dbg_image: Option<&[u8]>, + ) -> (); + + fn box_clone(&self) -> Box; +} + +impl Clone for Box { + fn clone(&self) -> Box { + self.box_clone() + } +} + +/// Default agent for unsupported profiling build. +#[derive(Debug, Default, Clone, Copy)] +pub struct NullProfilerAgent {} + +#[derive(Debug)] +struct NullProfilerAgentError; + +impl fmt::Display for NullProfilerAgentError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "A profiler agent is not supported by this build") + } +} + +// This is important for other errors to wrap this one. +impl Error for NullProfilerAgentError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + // Generic error, underlying cause isn't tracked. + None + } +} + +impl ProfilingAgent for NullProfilerAgent { + fn init(&mut self) -> Result<(), Box> { + Ok(()) + } + + fn module_load( + &mut self, + _module_name: &str, + _addr: *const u8, + _len: usize, + _dbg_image: Option<&[u8]>, + ) -> () { + } + + fn box_clone(&self) -> Box { + Box::new((*self).clone()) + } +} diff --git a/crates/runtime/Cargo.toml b/crates/runtime/Cargo.toml index 6f8e8c86086d..e6ac4792fb70 100644 --- a/crates/runtime/Cargo.toml +++ b/crates/runtime/Cargo.toml @@ -11,6 +11,7 @@ readme = "README.md" edition = "2018" [dependencies] +wasmtime-profiling = { path = "../profiling" } wasmtime-environ = { path = "../environ", version = "0.9.0" } region = "2.0.0" libc = { version = "0.2.60", default-features = false } diff --git a/src/lib.rs b/src/lib.rs index 60ea36230bb5..d4c8d63309ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,6 +30,7 @@ use anyhow::{bail, Result}; use std::path::PathBuf; use structopt::StructOpt; use wasmtime::{Config, Strategy}; +use wasmtime_profiling::ProfilingStrategy; fn pick_compilation_strategy(cranelift: bool, lightbeam: bool) -> Result { Ok(match (lightbeam, cranelift) { @@ -40,6 +41,13 @@ fn pick_compilation_strategy(cranelift: bool, lightbeam: bool) -> Result Result { + Ok(match jitdump { + true => ProfilingStrategy::JitDumpProfiler, + false => ProfilingStrategy::NullProfiler, + }) +} + fn init_file_per_thread_logger(prefix: &'static str) { file_per_thread_logger::initialize(prefix); @@ -117,6 +125,10 @@ struct CommonOptions { #[structopt(long, conflicts_with = "cranelift")] lightbeam: bool, + /// Generate jitdump file (supported on --features=profiling build) + #[structopt(long)] + jitdump: bool, + /// Run optimization passes on translated functions #[structopt(short = "O", long)] optimize: bool, @@ -133,7 +145,8 @@ impl CommonOptions { .wasm_reference_types(self.enable_reference_types || self.enable_all) .wasm_multi_value(self.enable_multi_value || self.enable_all) .wasm_threads(self.enable_threads || self.enable_all) - .strategy(pick_compilation_strategy(self.cranelift, self.lightbeam)?)?; + .strategy(pick_compilation_strategy(self.cranelift, self.lightbeam)?)? + .profiler(pick_profiling_strategy(self.jitdump)?)?; if self.optimize { config.cranelift_opt_level(wasmtime::OptLevel::Speed); } diff --git a/tests/instantiate.rs b/tests/instantiate.rs index 7b21cdf78465..b9bd6e1064eb 100644 --- a/tests/instantiate.rs +++ b/tests/instantiate.rs @@ -24,7 +24,7 @@ fn test_environ_translate() { let cache_config = CacheConfig::new_cache_disabled(); let mut compiler = Compiler::new(isa, CompilationStrategy::Auto, cache_config); unsafe { - let instance = instantiate(&mut compiler, &data, &mut resolver, false); + let instance = instantiate(&mut compiler, &data, &mut resolver, false, None); assert!(instance.is_ok()); } }