diff --git a/lisa/_assets/kmodules/kconfig_fragment.config b/lisa/_assets/kmodules/kconfig_fragment.config index 0af706c11790fd7f4612f45e6c63c170d02af1b0..1e5dec710753738c4f9fac125067ef6ef746654b 100644 --- a/lisa/_assets/kmodules/kconfig_fragment.config +++ b/lisa/_assets/kmodules/kconfig_fragment.config @@ -5,4 +5,3 @@ CONFIG_DEBUG_INFO_BTF=y CONFIG_DEBUG_INFO_REDUCED=n CONFIG_BPF_SYSCALL=y CONFIG_FTRACE=y -CONFIG_SYNTH_EVENTS=y diff --git a/lisa/_assets/kmodules/lisa/Makefile b/lisa/_assets/kmodules/lisa/Makefile index 033365cc38f084a9a3f30e69c240bb7db45c3543..c2490b68b8bdfb4067b2022eeacef88a55551f2e 100644 --- a/lisa/_assets/kmodules/lisa/Makefile +++ b/lisa/_assets/kmodules/lisa/Makefile @@ -79,6 +79,7 @@ RUST_OBJECT := $(RUST_GENERATED)/rust.o RUST_OBJECT_CMD := $(RUST_GENERATED)/.rust.o.cmd RUST_C_SHIMS_H := $(RUST_GENERATED)/rust_c_shims.h RUST_C_SHIMS_C := $(RUST_GENERATED)/rust_c_shims.c +RUST_TRACE_EVENTS_HEADER := $(RUST_GENERATED)/trace_events.h RUST_BUILD_DIR := $(RUST_GENERATED)/build RUST_C_SHIMS_DIR := $(RUST_BUILD_DIR)/rust_c_shims @@ -86,6 +87,8 @@ RUST_OBJECT_RAW := $(RUST_BUILD_DIR)/rust.raw.o RUST_SYMBOLS := $(RUST_BUILD_DIR)/exported.list RUST_SYMBOLS_CLI := $(RUST_BUILD_DIR)/exported.cli RUST_START_STOP_LDS := $(RUST_BUILD_DIR)/start_stop.lds +RUST_JSON_DATA_DIR := $(RUST_BUILD_DIR)/rust_data/ +RUST_JSON_DATA := $(RUST_BUILD_DIR)/rust_data.json RUST_CBINDGEN_H := $(RUST_SRC)/cbindgen.h RUST_CBINDGEN_BIN := $(CARGO_HOME)/bin/cbindgen @@ -107,7 +110,7 @@ export LISA_EVAL_C_CFLAGS=$(shell echo $(c_flags)) rust_cmd = chmod +x '$(LISA_EVAL_C)' && export RUSTC_BOOTSTRAP=1 PATH="$(CARGO_HOME)/bin:$$PATH" 'RUSTUP_HOME=$(RUSTUP_HOME)' 'CARGO_HOME=$(CARGO_HOME)' 'CARGO_TARGET_DIR=$(CARGO_TARGET_DIR)' 'LISA_EVAL_C=$(LISA_EVAL_C)' && $(1) cargo_cmd = $(call rust_cmd,cargo +$(RUST_VERSION) $(1)) -$(CARGO_TARGET_DIR) $(GENERATED) $(RUST_GENERATED) $(RUST_BUILD_DIR) $(RUST_C_SHIMS_DIR): +$(CARGO_TARGET_DIR) $(GENERATED) $(RUST_GENERATED) $(RUST_BUILD_DIR) $(RUST_C_SHIMS_DIR) $(RUST_JSON_DATA_DIR): mkdir -p "$@" # Build the rust code into a static archive, then prelink it into an object @@ -121,7 +124,20 @@ $(RUST_OBJECT_RAW): $(RUST_BUILD_DIR) $(CARGO_TARGET_DIR) $(LD) $(KBUILD_LDFLAGS) -nostdlib -r -o $(RUST_OBJECT_RAW) --whole-archive $(CARGO_TARGET_DIR)/target/release/liblisakmod.a -$(RUST_OBJECT): $(RUST_BUILD_DIR) $(RUST_OBJECT_RAW) +define extract-binstore + cp $(1) $(1)_objdump.o + $(OBJDUMP) -h $(1)_objdump.o | awk '{ print $$2 }' | grep '^.binstore.$(2)' | sort | xargs -r -P$$(nproc) -n1 sh -c '$(OBJCOPY) --dump-section $$0=$(3)/_$$0 $(1) /dev/null' +endef + + +$(RUST_JSON_DATA): $(RUST_BUILD_DIR) $(RUST_JSON_DATA_DIR) $(RUST_OBJECT_RAW) + + touch $(RUST_JSON_DATA) + $(call extract-binstore,$(RUST_OBJECT_RAW),json,$(RUST_JSON_DATA_DIR)) + LC_ALL=C cat $(RUST_JSON_DATA_DIR)/_.binstore.json > $(RUST_JSON_DATA) + + +$(RUST_OBJECT): $(RUST_GENERATED) $(RUST_BUILD_DIR) $(RUST_OBJECT_RAW) $(RUST_JSON_DATA) # Get: # * The list of exported symbols # * A linker script with __start_SECNAME and __stop_SECNAME symbols so @@ -130,7 +146,7 @@ $(RUST_OBJECT): $(RUST_BUILD_DIR) $(RUST_OBJECT_RAW) # executable or DSO (a .ko is a relocatable object file) and # therefore never getting those symbols created by the linker # automatically. - python3 "$(MODULE_SRC)/rust_exported_symbols.py" --rust-object $(RUST_OBJECT_RAW) --out-symbols-plain $(RUST_SYMBOLS) --out-symbols-cli $(RUST_SYMBOLS_CLI) --out-start-stop-lds $(RUST_START_STOP_LDS) + python3 "$(MODULE_SRC)/process_rust.py" --rust-object $(RUST_OBJECT_RAW) --json $(RUST_JSON_DATA) --out-symbols-plain $(RUST_SYMBOLS) --out-symbols-cli $(RUST_SYMBOLS_CLI) --out-start-stop-lds $(RUST_START_STOP_LDS) --out-trace-events-header $(RUST_TRACE_EVENTS_HEADER) # Garbage collect unused sections in the object file, after we have # extracted the binstore sections (otherwise they get discarded). @@ -150,20 +166,20 @@ $(RUST_OBJECT): $(RUST_BUILD_DIR) $(RUST_OBJECT_RAW) touch $(RUST_OBJECT_CMD) -$(RUST_C_SHIMS_H): $(RUST_OBJECT_RAW) $(RUST_C_SHIMS_DIR) +$(RUST_C_SHIMS_H): $(RUST_GENERATED) $(RUST_OBJECT_RAW) $(RUST_C_SHIMS_DIR) # Extract the C shims created by our inlinec Rust module from the # binary, so they can be compiled and linked later. # Create some empty files so that the globbing pattern later on always # matches something. - touch $(RUST_C_SHIMS_DIR)/shim_.binstore.c.header. $(RUST_C_SHIMS_DIR)/shim_.binstore.c.code. + touch $(RUST_C_SHIMS_DIR)/_.binstore.c.header. $(RUST_C_SHIMS_DIR)/_.binstore.c.code. - cp $(RUST_OBJECT_RAW) $(RUST_OBJECT_RAW)_objdump.o - $(OBJDUMP) -h $(RUST_OBJECT_RAW)_objdump.o | awk '{ print $$2 }' | grep '^.binstore.c.' | sort | xargs -P$$(nproc) -n1 sh -c '$(OBJCOPY) --dump-section $$0=$(RUST_C_SHIMS_DIR)/shim_$$0 $(RUST_OBJECT_RAW) /dev/null' - LC_ALL=C cat $(RUST_C_SHIMS_DIR)/shim_.binstore.c.header.* > $(RUST_C_SHIMS_H) - LC_ALL=C cat $(RUST_C_SHIMS_DIR)/shim_.binstore.c.code.* > $(RUST_C_SHIMS_C) + $(call extract-binstore,$(RUST_OBJECT_RAW),c.,$(RUST_C_SHIMS_DIR)) + LC_ALL=C cat $(RUST_C_SHIMS_DIR)/_.binstore.c.header.* > $(RUST_C_SHIMS_H) + LC_ALL=C cat $(RUST_C_SHIMS_DIR)/_.binstore.c.code.* > $(RUST_C_SHIMS_C) cat $(RUST_C_SHIMS_H) $(RUST_C_SHIMS_C) | if which clang-format 2>&1 1>/dev/null; then clang-format; else cat; fi +$(RUST_C_SHIMS_H): $(RUST_OBJECT_RAW) $(RUST_C_SHIMS_DIR) # This rule is necessary as Kbuild cannot cope with absolute paths on the # object file list to link in the module. Unfortunately, make is not smart diff --git a/lisa/_assets/kmodules/lisa/ftrace_events.h b/lisa/_assets/kmodules/lisa/ftrace_events.h index af991215376ea0a04cf7090dca6aa3341139f0a0..198d5ba83c41e975b7078c683ef551e7569e35b7 100644 --- a/lisa/_assets/kmodules/lisa/ftrace_events.h +++ b/lisa/_assets/kmodules/lisa/ftrace_events.h @@ -15,8 +15,11 @@ #include #include +#include "utils.h" #include "sched_helpers.h" +#include "generated/rust/trace_events.h" + #if HAS_MEMBER(struct, sched_avg, runnable_load_avg) #define RBL_LOAD_ENTRY rbl_load #define RBL_LOAD_MEMBER runnable_load_avg diff --git a/lisa/_assets/kmodules/lisa/process_rust.py b/lisa/_assets/kmodules/lisa/process_rust.py new file mode 100755 index 0000000000000000000000000000000000000000..3856a8302eed8bda418699bc557f0dfdb2e23413 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/process_rust.py @@ -0,0 +1,259 @@ +#!/usr/bin/env python3 +# +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (C) 2023, Arm Limited and contributors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import argparse +from pathlib import Path +from shlex import quote +import subprocess +import os +import textwrap +import json +from functools import cache +from operator import itemgetter +import textwrap +import itertools +import json + + +SEP = '\n ' + + +def parse_json(path): + content = Path(path).read_text() + print(f'JSON data:\n{content}') + return [ + json.loads(line) + for line in content.splitlines() + ] + + + +def main(): + parser = argparse.ArgumentParser(""" + Process the Rust object file and associated JSON data. + """) + + parser.add_argument('--rust-object', help='Built Rust object file') + parser.add_argument('--json', help='JSON data extracted from the Rust object file') + parser.add_argument('--out-symbols-plain', help='File to write the exported symbol list, one per line') + parser.add_argument('--out-symbols-cli', help='File to write the exported symbol list as ld CLI --undefined options') + parser.add_argument('--out-start-stop-lds', help='File to write the linker script to provide __start_SECNAME and __stop_SECNAME symbols, which are not provided by Kbuild since the module is never linked into an executable or DSO') + parser.add_argument('--out-trace-events-header', help='File to write the ftrace event definitions') + + args = parser.parse_args() + + @cache + def get_symbols(): + nm = os.environ.get('NM', 'nm') + path = args.rust_object + if path is None: + parser.error(f'--rust-object needs to be specified to get symbols list') + else: + out = subprocess.check_output( + [nm, '-gj', str(path)], + ) + + all_symbols = sorted(set(out.decode().split())) + return all_symbols + + @cache + def get_data(): + path = args.json + if path is None: + parser.error(f'--json needs to be specified for JSON-related features') + else: + return parse_json(path) + + @cache + def get_exported_symbols(): + # We create a JSON entry for each symbol we want to export in Rust. There + # unfortunately seems to be no cleaner way to convey the list of exported + # symbols from Rust code as of 2024. + user_symbols = [ + entry["symbol"] + for entry in get_data() + if entry["type"] == "export-symbol" + ] + symbols = sorted(user_symbols) + print(f'Found exported symbols:{SEP}{SEP.join(symbols)}') + return symbols + + if (path := args.out_symbols_plain): + content = '\n'.join(get_exported_symbols()) + '\n' + Path(path).write_text(content) + + if (path := args.out_symbols_cli): + content = ' '.join(( + f'--undefined {quote(sym)}' + for sym in get_exported_symbols() + )) + '\n' + Path(path).write_text(content) + + if (path := args.out_start_stop_lds): + def parse(sym, prefix): + if sym.startswith(prefix): + sym = sym[len(prefix):] + return sym + else: + return None + + sections = sorted({ + section + for sym in get_symbols() + if ( + (section := parse(sym, '__start_')) or + (section := parse(sym, '__stop_')) + ) + }) + print(f'Found sections to generate __start_SECNAME and __stop_SECNAME symbols for:{SEP}{SEP.join(sections)}') + + def make_lds(section): + return textwrap.dedent(f''' + SECTIONS {{ + {section} : {{ + PROVIDE(__start_{section} = .); + KEEP(*({section})); + PROVIDE(__stop_{section} = .); + }} + }} + ''') + + lds = '\n'.join(map(make_lds, sections)) + '\n' + Path(path).write_text(lds) + + if (path := args.out_trace_events_header): + + class Field: + def __init__(self, name, logical_type, c_arg_type): + self.name = name + self.logical_type = logical_type + self.c_arg_type = c_arg_type + + @property + def tp_struct_entry(self): + typ = self.logical_type + # TODO: support arrays + if typ == 'string': + return f'__string({self.name}, {self.name})' + elif typ in ('u8', 's8', 'u16', 's16', 'u32', 's32', 'u64', 's64'): + return f'__field({self.c_arg_type}, {self.name})' + else: + raise ValueError(f'Unsupported logical type: {typ}') + + @property + def entry(self): + return f'__entry->{self.name}' + + @property + def tp_fast_assign(self): + typ = self.logical_type + # TODO: support arrays + if typ == 'string': + return f'__lisa_assign_str({self.name}, {self.name});' + elif typ in ('u8', 's8', 'u16', 's16', 'u32', 's32', 'u64', 's64'): + return f'{self.entry} = {self.name};' + else: + raise ValueError(f'Unsupported logical type: {typ}') + + @property + def tp_printk(self): + typ = self.logical_type + # TODO: support arrays + if typ in ('s8', 's16', 's32'): + return (f'{self.name}=%d', [self.entry]) + elif typ in ('u8', 'u16', 'u32'): + return (f'{self.name}=%u', [self.entry]) + elif typ == 's64': + return (f'{self.name}=%lld', [self.entry]) + elif typ == 'u64': + return (f'{self.name}=%llu', [self.entry]) + elif typ == 'string': + return (f'{self.name}=%s', [f'__get_str({self.name})']) + else: + raise ValueError(f'Unsupported logical type: {typ}') + + def make_event(entry): + name = entry['name'] + fields = [ + Field( + name=field['name'], + logical_type=field['logical-type'], + c_arg_type=field['c-arg-type'], + ) + for field in entry['fields'] + ] + + nl = '\n ' + proto = ', '.join( + f'__typeof__({field.c_arg_type}) {field.name}' + for field in fields + ) + args = ', '.join( + field.name + for field in fields + ) + struct_entry = nl.join( + field.tp_struct_entry + for field in fields + ) + + assign = nl.join( + field.tp_fast_assign + for field in fields + ) + printk_fmts, printk_args = zip(*( + field.tp_printk + for field in fields + )) + printk_fmt = ' '.join(printk_fmts) + # Use json escaping as an easy way to produce a C string literal. + printk_fmt = json.dumps(printk_fmt) + printk_args = ', '.join(itertools.chain.from_iterable(printk_args)) + + return textwrap.dedent(f''' + TRACE_EVENT({name}, + TP_PROTO({proto}), + TP_ARGS({args}), + TP_STRUCT__entry( + {struct_entry} + ), + TP_fast_assign( + {assign} + ), + TP_printk( + {printk_fmt}, {printk_args} + ) + ) + ''') + + events = sorted( + ( + entry + for entry in get_data() + if entry["type"] == "define-ftrace-event" + ), + key=itemgetter('name'), + ) + out = '\n\n'.join(map(make_event, events)) + Path(path).write_text(out) + + + +if __name__ == '__main__': + main() diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/Cargo.lock b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/Cargo.lock index b99020f2a499ae0e59168d6306dd4f460d558158..76baf9ec28ebf7afe037140838ae6f610cf557c3 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/Cargo.lock +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/Cargo.lock @@ -2,28 +2,50 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "getrandom" -version = "0.2.15" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", "libc", + "r-efi", "wasi", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "libc" -version = "0.2.169" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "lisakmod_macros" @@ -38,6 +60,7 @@ name = "lisakmod_macros_proc" version = "0.1.0" dependencies = [ "getrandom", + "itertools", "proc-macro2", "quote", "syn", @@ -51,27 +74,33 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "syn" -version = "2.0.92" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ae51629bf965c5c098cc9e87908a3df5301051a9e087d6f9bef5c9771ed126" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -80,12 +109,24 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/Cargo.lock b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/Cargo.lock index 4745fa07a58743b395646ce3d60332f0514ebb83..cf1c7160a25c6d9bd94fa392e392d7a160ba7d33 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/Cargo.lock +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/Cargo.lock @@ -2,34 +2,57 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "getrandom" -version = "0.2.15" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", "libc", + "r-efi", "wasi", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "libc" -version = "0.2.164" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "lisakmod_macros_proc" version = "0.1.0" dependencies = [ "getrandom", + "itertools", "proc-macro2", "quote", "syn", @@ -37,27 +60,33 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.37" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "syn" -version = "2.0.89" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -66,12 +95,24 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/Cargo.toml b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/Cargo.toml index e20f520626918266f05c5ee559b8ee6f8073a5ce..08e9bbfdcc5882a8685eac681d5d6b9238ae3f72 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/Cargo.toml +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/Cargo.toml @@ -7,7 +7,8 @@ edition = "2024" syn = { version = "2.0", default-features = false, features = ["proc-macro", "parsing", "printing", "full", "clone-impls"]} quote = { version = "1.0", default-features = false } proc-macro2 = { version = "1.0", default-features = false, features = ["span-locations"]} -getrandom = {version = "0.2", default-features = false } +getrandom = {version = "0.3.2", default-features = false } +itertools = "0.14.0" [lib] proc-macro = true diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/src/inlinec.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/src/inlinec.rs index f69d304ec91231af1582425f2e75c692c5775089..aa162b19876acd9912806bfd148650b812229336 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/src/inlinec.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/src/inlinec.rs @@ -10,7 +10,13 @@ use syn::{ parse::Parse, parse_quote, punctuated::Punctuated, spanned::Spanned, token, }; -use crate::misc::{concatcp, get_random}; +use crate::misc::{_dump_to_binstore, concatcp, get_random}; + +fn _export_symbol(sym: Ident) -> Result { + Ok(quote! { + ::lisakmod_macros::misc::export_symbol!(#sym); + }) +} struct CFuncInput { name: Ident, @@ -131,8 +137,8 @@ fn make_c_func( )?; let c_out = [ - make_c_out(c_out, &format!(".binstore.c.code.{}", c_name))?, - make_c_out(c_header_out, &format!(".binstore.c.header.{}", c_name))?, + _dump_to_binstore(&format!("c.code.{}", c_name), c_out)?, + _dump_to_binstore(&format!("c.header.{}", c_name), c_header_out)?, ]; Ok(quote! { @@ -664,38 +670,6 @@ pub fn cexport(attrs: TokenStream, code: TokenStream) -> Result Result { - let ident = syn::parse2::(args)?; - _export_symbol(ident) -} - -fn _export_symbol(ident: Ident) -> Result { - let marker = format_ident!("__export_rust_symbol_{ident}"); - - Ok(quote! { - const _:() = { - #[used] - #[unsafe(no_mangle)] - static #marker: () = (); - }; - }) -} - -fn make_c_out(c_code: TokenStream, section: &str) -> Result { - Ok(quote! { - const _: () = { - const CODE_SLICE: &[u8] = #c_code.as_bytes(); - const CODE_LEN: usize = CODE_SLICE.len(); - - // Store the C function in a section of the binary, that will be extracted by the - // module Makefile and compiled separately as C code. - #[unsafe(link_section = #section)] - #[used] - static CODE: [u8; CODE_LEN] = ::lisakmod_macros::private::misc::slice_to_array::<{CODE_LEN}>(CODE_SLICE); - }; - }) -} - struct CStaticInput { name: Ident, c_code: ( @@ -810,7 +784,7 @@ pub fn cstatic(attrs: TokenStream, code: TokenStream) -> Result Result TokenStream { convert(inlinec::cconstant(args.into())) } -#[proc_macro] -pub fn export_symbol(args: TokenStream) -> TokenStream { - convert(inlinec::export_symbol(args.into())) +#[proc_macro_attribute] +pub fn cstatic(attrs: TokenStream, code: TokenStream) -> TokenStream { + convert(inlinec::cstatic(attrs.into(), code.into())) } #[proc_macro] @@ -39,7 +40,12 @@ pub fn concatcp(args: TokenStream) -> TokenStream { convert(misc::concatcp(args.into())) } -#[proc_macro_attribute] -pub fn cstatic(attrs: TokenStream, code: TokenStream) -> TokenStream { - convert(inlinec::cstatic(attrs.into(), code.into())) +#[proc_macro] +pub fn dump_to_binstore(args: TokenStream) -> TokenStream { + convert(misc::dump_to_binstore(args.into())) +} + +#[proc_macro] +pub fn json_metadata(args: TokenStream) -> TokenStream { + convert(misc::json_metadata(args.into())) } diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/src/misc.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/src/misc.rs index 291ae63f7068020f153636deb2fcb835fb5b6357..afdb8578e2c34156e0133dea282d3aa9eb5f6b57 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/src/misc.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/src/misc.rs @@ -1,14 +1,26 @@ /* SPDX-License-Identifier: Apache-2.0 */ -use proc_macro2::TokenStream; +use itertools::Itertools as _; +use proc_macro2::{Delimiter, TokenStream, TokenTree}; use quote::{format_ident, quote}; -use syn::{Error, Expr, Ident, Token, punctuated::Punctuated}; +use syn::{Error, Expr, ExprLit, Ident, Lit, Token, punctuated::Punctuated, spanned::Spanned}; + +fn try_expand(tokens: TokenStream) -> Result { + let tokens: proc_macro::TokenStream = tokens.into(); + match tokens.expand_expr() { + // As of 03/2025, TokenStream::expand_expr() will not evaluate + // const expressions (e.g. trait associated constants), only + // macros. + Err(_) => Err(tokens.into()), + Ok(tokens) => Ok(tokens.into()), + } +} // Use getrandom instead of e.g. uuid crate as it has far fewer dependencies, so faster build // time. pub(crate) fn get_random() -> u128 { let mut buf: [u8; 128 / 8] = [0; 128 / 8]; - getrandom::getrandom(&mut buf).expect("Could not get random number"); + getrandom::fill(&mut buf).expect("Could not get random number"); u128::from_le_bytes(buf) } @@ -24,6 +36,52 @@ pub(crate) fn concatcp(args: TokenStream) -> Result { .collect() }; + // Concatenate all string literals we find beforehand to make the generate code more compact. + #[allow(clippy::large_enum_variant)] + enum Item { + LitStr(String), + Other(Expr), + } + impl quote::ToTokens for Item { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Item::LitStr(s) => s.to_tokens(tokens), + Item::Other(expr) => expr.to_tokens(tokens), + } + } + } + + let items: Vec<_> = items + .into_iter() + .map(|item| match try_expand(quote! {#item}) { + Ok(expr) => match syn::parse2::(expr) { + Ok(Expr::Lit(ExprLit { + lit: Lit::Str(litstr), + .. + })) => Item::LitStr(litstr.value()), + _ => Item::Other(item), + }, + Err(_) => Item::Other(item), + }) + .chunk_by(|item| matches!(item, Item::LitStr(_))) + .into_iter() + .flat_map(|(is_lit, chunk)| { + if is_lit { + vec![Item::LitStr( + chunk + .into_iter() + .map(|item| match item { + Item::LitStr(s) => s, + _ => unreachable!(), + }) + .collect(), + )] + } else { + chunk.into_iter().collect() + } + }) + .collect(); + let item_names: Vec = items .iter() .map(|_| format_ident!("__concat_item_{:0>39}", get_random())) @@ -58,3 +116,116 @@ pub(crate) fn concatcp(args: TokenStream) -> Result { // eprintln!("OUT {out}"); Ok(out) } + +pub fn dump_to_binstore(args: TokenStream) -> Result { + let _args = if args.is_empty() { + Vec::new() + } else { + syn::parse::Parser::parse2( + Punctuated::::parse_terminated, + args.clone(), + )? + .into_iter() + .collect() + }; + match &_args[..] { + [subsection, content] => __dump_to_binstore(quote! {#subsection}, quote! {#content}), + _ => Err(Error::new( + args.span(), + "The usage is: dump_to_binstore!(, \"content\") ", + )), + } +} + +pub(crate) fn _dump_to_binstore( + subsection: &str, + content: TokenStream, +) -> Result { + __dump_to_binstore(quote! {#subsection}, content) +} + +pub fn __dump_to_binstore( + subsection: TokenStream, + content: TokenStream, +) -> Result { + Ok(quote! { + const _: () = { + // Allow a TokenStream input so we allow e.g. macro calls etc, rather than just string + // literals. + const CONTENT_STR: &str = #content; + const CONTENT_SLICE: &[u8] = CONTENT_STR.as_bytes(); + const CONTENT_LEN: usize = CONTENT_SLICE.len(); + + // Store the C function in a section of the binary, that will be extracted by the + // module Makefile and the processed (e.g. compiled for C code). + #[unsafe(link_section = ::core::concat!(".binstore.", #subsection))] + #[used] + static CONTENT_ARRAY: [u8; CONTENT_LEN] = { + let mut arr = [0u8; CONTENT_LEN]; + let mut idx: usize = 0; + while idx < CONTENT_LEN { + arr[idx] = CONTENT_SLICE[idx]; + idx += 1; + } + arr + }; + }; + }) +} + +pub fn json_metadata(args: TokenStream) -> Result { + fn process(stream: TokenStream) -> Result { + let tokens: Result, Error> = stream + .into_iter() + .map(|tt| match tt { + TokenTree::Group(ref grp) => { + match grp.delimiter() { + Delimiter::Parenthesis => { + // Allow input to contain parenthesized groups that will be expanded, + // rather than directly stringified to form the JSON output. + let grp = grp.stream(); + match try_expand(grp) { + Err(grp) => { + // Poor man's stringify!(). Ideally, we would evaluate grp to a + // string, and then quote it according to JSON rules. + // Unfortunately, we can't do that easily in a const fn for + // now, so we just add quotes around and hope for the best. + Ok(quote! { "\"", #grp, "\"" }) + } + Ok(grp) => { + // We evaluated to a literal, and now we can stringify!() it so + // that it can be concatenated to the rest. This relies on a + // the Rust native string syntax to be acceptable to a JSON + // parser. Otherwise, we would need to convert the evaluated + // literal to something JSON is happy with, but we cannot do + // that easily. + Ok(quote! {::core::stringify!(#grp)}) + } + } + } + // Braces are allowed as part of the JSON syntax, we just recurse inside + Delimiter::Brace => { + let grp = process(grp.stream())?; + Ok(quote! { "{", #grp, "}" }) + } + Delimiter::Bracket => { + let grp = process(grp.stream())?; + Ok(quote! { "[", #grp, "]" }) + } + Delimiter::None => process(grp.stream()), + } + } + tt => Ok(quote! { ::core::stringify!(#tt) }), + }) + .collect(); + let tokens = tokens?; + Ok(quote! { #(#tokens),* }) + } + let tokens = process(args)?; + let tokens = if tokens.is_empty() { + quote! { "{}" } + } else { + quote! { ::lisakmod_macros::misc::concatcp!(#tokens, "\n") } + }; + _dump_to_binstore("json", tokens) +} diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/inlinec.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/inlinec.rs index 65cd2be39f8605aba314a3a51e181b59156b9d88..46df4b1f7b1cfe517780ebaba15ae7f032432a69 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/inlinec.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/inlinec.rs @@ -1065,7 +1065,9 @@ make_getaligned!( 262144, 524288 ); -pub trait Opaque { +pub trait Opaque {} + +pub trait SizedOpaque: Opaque { /// # Safety /// /// The passed `init` function must initialize fully the new value, so that calling @@ -1152,7 +1154,7 @@ macro_rules! __internal_opaque_type { // this member. The member in question is an array of u8, which is FFI-safe. #[repr(transparent)] $vis struct $name { - // Since we cannot make Opaque types aligned with a simple attribute + // Since we cannot make opaque types aligned with a simple attribute // (#[repr(align(my_macro!()))] is rejected since my_macro!() is not an integer // literal), we add a zero-sized member that allows specifying the alignment as a // generic const parameter. @@ -1209,8 +1211,10 @@ macro_rules! __internal_opaque_type { $crate::inlinec::__impl_primitive_ptr!($name, $c_name, Some($c_header)); - use $crate::inlinec::Opaque as _; + use $crate::inlinec::{Opaque as _, SizedOpaque as _}; impl $crate::inlinec::Opaque for $name {} + impl $crate::inlinec::SizedOpaque for $name {} + impl $crate::inlinec::FfiType for $name { type FfiType = $name; const C_TYPE: &'static str = $c_name; @@ -1337,6 +1341,30 @@ macro_rules! __internal_opaque_type { // namespace under its private name). pub use crate::__internal_opaque_type as opaque_type; +#[macro_export] +macro_rules! __internal_incomplete_opaque_type { + ($vis:vis struct $name:ident, $c_name:literal, $c_header:expr) => { + #[repr(transparent)] + $vis struct $name { + _data: ::core::ffi::c_void, + } + + $crate::inlinec::__impl_primitive_ptr!($name, $c_name, Some($c_header)); + + use $crate::inlinec::Opaque as _; + impl $crate::inlinec::Opaque for $name {} + + // No FromFfi or IntoFfi implementations as we cannot manipulate values directly. We + // however do provide implementation for reference and pointer types + impl $crate::inlinec::FfiType for $name { + type FfiType = $name; + const C_TYPE: &'static str = $c_name; + const C_HEADER: Option<&'static str> = Some($c_header); + } + }; +} +pub use crate::__internal_incomplete_opaque_type as incomplete_opaque_type; + #[macro_export] macro_rules! __internal_c_static_assert { ($headers:literal, $expr:tt) => {{ @@ -1355,3 +1383,19 @@ macro_rules! __internal_c_static_assert { }}; } pub use crate::__internal_c_static_assert as c_static_assert; + +#[macro_export] +macro_rules! __internal_c_eval { + ($header:expr, $expr:literal, $ty:ty) => {{ + // Emit the C function code that will be extracted from the Rust object file and then + // compiled as C. + #[$crate::inlinec::cfunc] + #[allow(non_snake_case)] + fn snippet() -> $ty { + concat!("#include<", $header, ">"); + concat!("return (", $expr, ");") + } + snippet() + }}; +} +pub use crate::__internal_c_eval as c_eval; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/misc.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/misc.rs index ac505dbb5318ea9cbe31f6c3b1410204439d7345..233710f3f7ae0c6224c370f98b53f48a5df21ce4 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/misc.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/misc.rs @@ -1,2 +1,13 @@ /* SPDX-License-Identifier: Apache-2.0 */ -pub use lisakmod_macros_proc::concatcp; +pub use lisakmod_macros_proc::{concatcp, dump_to_binstore, json_metadata}; + +#[macro_export] +macro_rules! __internal_export_symbol { + ($sym:ident) => { + $crate::misc::json_metadata!({ + "type": "export-symbol", + "symbol": (::core::stringify!($sym)) + }); + }; +} +pub use crate::__internal_export_symbol as export_symbol; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/private/misc.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/private/misc.rs index 5f9e78486d9a1d72421f45e96158c4bb3862d6e3..76f56fc2f9be40b4c597c0a8c3080d2005b1b91e 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/private/misc.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/private/misc.rs @@ -22,13 +22,3 @@ pub const fn concat(slices: &[&'static str]) -> [u8; N] { } out } - -pub const fn slice_to_array(slice: &[u8]) -> [u8; N] { - let mut arr = [0u8; N]; - let mut idx: usize = 0; - while idx < N { - arr[idx] = slice[idx]; - idx += 1; - } - arr -} diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.lock b/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.lock index 40e3bc50cb9a910c131a6e32083487eb10992605..69a2caee6df9d7f8e8e4f35fc68ad6cb3a086c39 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.lock +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.lock @@ -4,9 +4,15 @@ version = 4 [[package]] name = "anyhow" -version = "1.0.95" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "cfg-if" @@ -16,50 +22,51 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "either" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "getrandom" -version = "0.2.15" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", "libc", + "r-efi", "wasi", ] [[package]] name = "itertools" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] [[package]] name = "libc" -version = "0.2.169" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "linkme" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "566336154b9e58a4f055f6dd4cbab62c7dc0826ce3c0a04e63b2d2ecd784cdae" +checksum = "22d227772b5999ddc0690e733f734f95ca05387e329c4084fe65678c51198ffe" dependencies = [ "linkme-impl", ] [[package]] name = "linkme-impl" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edbe595006d355eaf9ae11db92707d4338cd2384d16866131cc1afdbdd35d8d9" +checksum = "71a98813fa0073a317ed6a8055dcd4722a49d9b862af828ee68449adb799b6be" dependencies = [ "proc-macro2", "quote", @@ -74,6 +81,7 @@ dependencies = [ "itertools", "linkme", "lisakmod_macros", + "pin-project", ] [[package]] @@ -89,6 +97,7 @@ name = "lisakmod_macros_proc" version = "0.1.0" dependencies = [ "getrandom", + "itertools", "proc-macro2", "quote", "syn", @@ -100,29 +109,55 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "syn" -version = "2.0.92" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ae51629bf965c5c098cc9e87908a3df5301051a9e087d6f9bef5c9771ed126" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -131,12 +166,24 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.toml b/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.toml index dac8d0ad629fb61d71b210073de0365e0121d85c..05240ea55ede5d8eaad1ced0f9a3f178e3c004b4 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.toml +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.toml @@ -8,10 +8,11 @@ crate-type = ["staticlib"] [dependencies] anyhow = { version = "1.0", default-features = false } -itertools = {version = "0.13", default-features = false, features = ["use_alloc"]} +itertools = {version = "0.14", default-features = false, features = ["use_alloc"]} linkme = "0.3.31" # hashbrown = "0.15" lisakmod_macros = { path = "../lisakmod-macros" } +pin-project = "1.1" [profile.release] panic = 'abort' diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/legacy.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/legacy.rs index 8194e027ff6a56e8dbc096c8825f40aa62e9b573..3e4e1f4c5159cae65bcb140e93163dbedfec2ecf 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/legacy.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/legacy.rs @@ -3,9 +3,10 @@ use alloc::{sync::Arc, vec::Vec}; use core::ffi::c_int; +use lisakmod_macros::inlinec::cfunc; + use crate::{ - error::error, features::define_feature, inlinec::cfunc, lifecycle::new_lifecycle, - runtime::printk::pr_err, + error::error, features::define_feature, lifecycle::new_lifecycle, runtime::printk::pr_err, }; define_feature! { diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/mod.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/mod.rs index 647d646574339bc6cf9af0be6ffa262ab67f70e7..a453d27febeca6fb55d9bed9a3d5c7f5c6411fd7 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/mod.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/mod.rs @@ -5,6 +5,7 @@ pub mod legacy; pub mod pixel6; pub mod tests; pub mod tracepoint; +pub mod wq; use alloc::{sync::Arc, vec::Vec}; use core::{ @@ -42,7 +43,35 @@ where { type Value = Arc<::Service>; } -pub type FeaturesService = typemap::TypeMap; + +pub struct FeaturesService { + map: typemap::TypeMap, +} + +impl FeaturesService { + #[inline] + fn new() -> FeaturesService { + FeaturesService { + map: typemap::TypeMap::new(), + } + } + + #[inline] + pub fn get(&self) -> Option<&::Service> + where + Feat: 'static + Feature, + { + self.map.get::().map(|service| &**service) + } + + #[inline] + pub fn insert(&mut self, service: Arc<::Service>) + where + Feat: 'static + Feature, + { + self.map.insert::(service) + } +} type LifeCycleAlias = LifeCycle::Service>, Error>; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/tests.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/tests.rs index 0b5ff6af3fa3d73491d24cdc482f76b2bca4578c..3d7f3267cca427a797cf3152ea7ece34001ba8c1 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/tests.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/tests.rs @@ -3,10 +3,11 @@ use alloc::{collections::BTreeMap, sync::Arc, vec, vec::Vec}; use core::ffi::CStr; +use lisakmod_macros::inlinec::{c_eval, cconstant, cfunc}; + use crate::{ error::Error, features::define_feature, - inlinec::{c_eval, cconstant, cfunc}, lifecycle::new_lifecycle, runtime::{ kbox::KernelKBox, diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/wq.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/wq.rs new file mode 100644 index 0000000000000000000000000000000000000000..2c05a51aede81a6f76243c13ddf58cb841da1178 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/wq.rs @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +use alloc::{sync::Arc, vec::Vec}; + +pub use crate::runtime::wq::*; +use crate::{features::define_feature, lifecycle::new_lifecycle}; + +#[derive(Debug)] +pub struct WqService { + wq: Wq, +} + +impl WqService { + fn new() -> WqService { + WqService { wq: Wq::new() } + } + + pub fn wq(&self) -> &Wq { + &self.wq + } +} + +define_feature! { + pub struct WqFeature, + name: "__wq", + visibility: Private, + Service: WqService, + Config: (), + dependencies: [], + init: |configs| { + Ok(new_lifecycle!(|services| { + yield_!(Ok(Arc::new(WqService::new()))); + Ok(()) + })) + }, +} diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/inlinec.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/inlinec.rs deleted file mode 100644 index 6c68998fda1e6c0541a5019f5420df491b0eadf8..0000000000000000000000000000000000000000 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/inlinec.rs +++ /dev/null @@ -1,18 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -pub use lisakmod_macros::inlinec::*; - -macro_rules! c_eval { - ($header:expr, $expr:literal, $ty:ty) => {{ - // Emit the C function code that will be extracted from the Rust object file and then - // compiled as C. - #[::lisakmod_macros::inlinec::cfunc] - #[allow(non_snake_case)] - fn snippet() -> $ty { - concat!("#include<", $header, ">"); - concat!("return (", $expr, ");") - } - snippet() - }}; -} -pub(crate) use c_eval; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lib.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lib.rs index b2000b35f51f10b8db0c3bedbe804419c0b22b8e..7c779ff1c851dbaf719568a8e89eec81c740f99b 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lib.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lib.rs @@ -17,8 +17,8 @@ pub mod features; pub mod fmt; pub mod graph; pub mod init; -pub mod inlinec; pub mod lifecycle; +pub mod mem; pub mod misc; pub mod prelude; pub mod registry; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/mem.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/mem.rs new file mode 100644 index 0000000000000000000000000000000000000000..1427bb0df2fb54cc15a7bc7efae6df3dd4ebf33d --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/mem.rs @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +macro_rules! container_of { + ($container:ty, $member:ident, $ptr:expr) => {{ + let ptr = $ptr; + let ptr: *const _ = (&*ptr); + let offset = core::mem::offset_of!($container, $member); + // SAFETY: Our contract is that c_kobj must be the member of a KObjectInner, so we can + // safely compute the pointer to the parent. + let container: *const $container = (ptr as *const $container).byte_sub(offset); + container + }}; +} +pub(crate) use container_of; + +#[allow(unused_macros)] +macro_rules! mut_container_of { + ($container:ty, $member:ident, $ptr:expr) => {{ $crate::mem::container_of!($container, $member, $ptr) as *mut $container }}; +} +#[allow(unused_imports)] +pub(crate) use mut_container_of; + +pub trait FromContained { + unsafe fn from_contained(contained: *const Contained) -> *const Self; +} + +macro_rules! impl_from_contained { + (($($generic:tt)*) $ty:ty, $attr:ident: $attr_ty:ty) => { + impl<$($generic)*> $crate::mem::FromContained<$attr_ty> for $ty { + unsafe fn from_contained(contained: *const $attr_ty) -> *const Self { + unsafe { + $crate::mem::container_of!( + Self, $attr, contained + ) + } + } + } + } +} +#[allow(unused_imports)] +pub(crate) use impl_from_contained; + +macro_rules! destructure { + ($value:expr, $($field:ident),*) => {{ + // Ensure there is no duplicate in the list of fields. If there is any duplicate, the code + // will not compile as parameter names cannot be duplicated. On top of that we even get a + // nice error message about duplicated parameters for the user of the macro ! + { + #[allow(unused)] + fn check_duplicates($($field: ()),*){} + } + let value = $value; + let value = ::core::mem::MaybeUninit::new(value); + let value = ::core::mem::MaybeUninit::as_ptr(&value); + ( + $( + // SAFETY: Once the value is wrapped in MaybeUninit, no custom Drop implementation + // will run anymore, so there is no risk of a Drop implementation to read from any + // of the attributes we moved out of. + // + // We also need to ensure that we never move out of the same field twice, which was + // checked earlier by ensuring there is no duplicated in the fields list. + unsafe { + core::ptr::read(&(*value).$field) + }, + )* + ) + }} +} +pub(crate) use destructure; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/misc.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/misc.rs index 8921d0b69ff42cfcd9f928393c5093d99e9662c9..db0c8a295a609a5dd7dc77e8a4701d0457082f03 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/misc.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/misc.rs @@ -1,50 +1,9 @@ /* SPDX-License-Identifier: GPL-2.0 */ -macro_rules! container_of { - ($container:ty, $member:ident, $ptr:expr) => {{ - let ptr = $ptr; - let ptr: *const _ = (&*ptr); - let offset = core::mem::offset_of!($container, $member); - // SAFETY: Our contract is that c_kobj must be the member of a KObjectInner, so we can - // safely compute the pointer to the parent. - let container: *const $container = (ptr as *const $container).byte_sub(offset); - container - }}; +// Join the parameters using the given separator, avoiding any trailing separator +macro_rules! join{ + ($sep:expr, $first:expr $(, $rest:expr)* $(,)?) => { + ::core::concat!($first $(, $sep, $rest)*) + }; } -pub(crate) use container_of; - -#[allow(unused_macros)] -macro_rules! mut_container_of { - ($container:ty, $member:ident, $ptr:expr) => {{ $crate::misc::container_of!($container, $member, $ptr) as *mut $container }}; -} -#[allow(unused_imports)] -pub(crate) use mut_container_of; - -macro_rules! destructure { - ($value:expr, $($field:ident),*) => {{ - // Ensure there is no duplicate in the list of fields. If there is any duplicate, the code - // will not compile as parameter names cannot be duplicated. On top of that we even get a - // nice error message about duplicated parameters for the user of the macro ! - { - #[allow(unused)] - fn check_duplicates($($field: ()),*){} - } - let value = $value; - let value = ::core::mem::MaybeUninit::new(value); - let value = ::core::mem::MaybeUninit::as_ptr(&value); - ( - $( - // SAFETY: Once the value is wrapped in MaybeUninit, no custom Drop implementation - // will run anymore, so there is no risk of a Drop implementation to read from any - // of the attributes we moved out of. - // - // We also need to ensure that we never move out of the same field twice, which was - // checked earlier by ensuring there is no duplicated in the fields list. - unsafe { - core::ptr::read(&(*value).$field) - }, - )* - ) - }} -} -pub(crate) use destructure; +pub(crate) use join; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/prelude.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/prelude.rs index 35c56c56da37459cd18dd04ea2ff523b7f98ab79..f5c3793697b1874404484db1d6b5d9a270e64084 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/prelude.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/prelude.rs @@ -2,11 +2,11 @@ #[allow(unused_imports)] pub(crate) use ::alloc::boxed::Box; +#[allow(unused_imports)] +pub(crate) use lisakmod_macros::inlinec::{c_eval, cconstant, cexport, cfunc, cstatic}; #[allow(unused_imports)] pub(crate) use crate::{ - inlinec::c_eval, - inlinec::{cconstant, cexport, cfunc, cstatic}, runtime::kbox::{KBox, KernelKBox}, runtime::printk::{pr_err, pr_info}, }; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/alloc.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/alloc.rs index ee993ac10a43b3d477b4f2280cb02b772b1bfc93..b4833aaf7463e7a84848eaf98a5e92d258745920 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/alloc.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/alloc.rs @@ -6,10 +6,9 @@ use core::{ ffi::c_void, }; -use crate::{ - inlinec::{cconstant, cfunc}, - runtime::printk::pr_err, -}; +use lisakmod_macros::inlinec::{cconstant, cfunc}; + +use crate::runtime::printk::pr_err; #[inline] fn with_size *mut u8>(layout: Layout, f: F) -> *mut u8 { diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/kbox.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/kbox.rs index 76b02a1bc34bac7a4afb25486a01c70847fff6f0..06049f88f982873b0c9b982ae1fdc58008a362f2 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/kbox.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/kbox.rs @@ -18,7 +18,7 @@ use core::{ use lisakmod_macros::inlinec::{FfiType, FromFfi, IntoFfi, MutPtr, Opaque}; use crate::{ - misc::destructure, + mem::destructure, runtime::alloc::{GFPFlags, KernelAlloc, KmallocAllocator}, }; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/mod.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/mod.rs index 28b0b7058b313edc114f2588c572d44245a1ce53..4ce864bf00837bbc9faafa1c5d97734c79bb1248 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/mod.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/mod.rs @@ -9,3 +9,9 @@ pub mod sync; pub mod sysfs; pub mod traceevent; pub mod tracepoint; +pub mod wq; + +// FIXME: remove ? +// This module is not currently used, as synthetic trace events are not actually supported +// upstream. +// pub mod synth_traceevent; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/panic.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/panic.rs index 2ddae08a7ad390684426b978aef0a36707eeecd7..23880cd0fcdb396a4cdce53fa23c1b1788527f58 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/panic.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/panic.rs @@ -2,9 +2,10 @@ use core::fmt::Write; +use lisakmod_macros::inlinec::cfunc; + use crate::{ fmt::{KBoxWriter, PRINT_PREFIX}, - inlinec::cfunc, runtime::alloc::{GFPFlags, KmallocAllocator}, }; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/printk.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/printk.rs index c7c8800bd773fbfff0ef865597052ad7fe6f401f..ebd457f0a2bcb38a61cef90b8b6f1a3031e8aece 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/printk.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/printk.rs @@ -2,9 +2,10 @@ use core::fmt::Write as _; +use lisakmod_macros::inlinec::cfunc; + use crate::{ fmt::{KBoxWriter, PRINT_PREFIX}, - inlinec::cfunc, runtime::alloc::{GFPFlags, KmallocAllocator}, }; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sync.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sync.rs index 8ae5edd10727e7ea7aedca2c72fd816103f9603a..5171f324079f77121b890ae9fc8d8b48f154dfe6 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sync.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sync.rs @@ -9,10 +9,9 @@ use core::{ pin::Pin, }; -use crate::{ - inlinec::{cfunc, opaque_type}, - runtime::kbox::KernelKBox, -}; +use lisakmod_macros::inlinec::{cfunc, opaque_type}; + +use crate::{mem::impl_from_contained, runtime::kbox::KernelKBox}; opaque_type!(struct CLockClassKey, "struct lock_class_key", "linux/lockdep.h"); @@ -85,39 +84,106 @@ impl Drop for LockdepClass { } } -pub trait LockGuard<'guard, T> +pub trait LockGuard<'guard> where - Self: Deref, + Self: Deref, { + type T; } -pub trait Lock +pub trait Lock where Self: Sync, { - type Guard<'a>: LockGuard<'a, T> + type T; + type Guard<'a>: LockGuard<'a, T = Self::T> where Self: 'a; fn lock(&self) -> Self::Guard<'_>; #[inline] - fn with_lock U>(&self, f: F) -> U { + fn with_lock U>(&self, f: F) -> U { f(self.lock().deref()) } } #[allow(private_bounds)] -pub trait LockMut: for<'a> _LockMut<'a, T> {} +pub trait LockMut: for<'a> _LockMut<'a> {} // TODO: We need to share the 'a lifetime between the Guard<'a> type and Self: 'a. Unfortunately, // there seems to be no direct way of doing that since a for<> lifetime only affects what comes // immediately after it. As a workaround, we can just hide that in a trait, and then use -// for<'a>_LockMut<'a, T> in the user-exposed trait. -trait _LockMut<'a, T> +// for<'a>_LockMut<'a> in the user-exposed trait. +trait _LockMut<'a> +where + ::Guard<'a>: DerefMut, + Self: 'a + Lock, +{ +} + +pub struct PinnedGuard<'a, L> +where + L: Lock + 'a, +{ + guard: ::Guard<'a>, +} + +impl<'a, L> PinnedGuard<'a, L> +where + L: Lock + 'a, +{ + #[inline] + pub fn as_ref(&self) -> Pin<&::T> { + // SAFETY: The guard we have can only come from a pinned lock, and that lock is hidden + // internally so that the only way to lock it is via a pinned reference. Since we do not + // allow unlocking that lock via a regular reference, it is not possible to circumvent the + // pinning guarantee and get a plain &mut T with Pin::get_ref(&Lock). + unsafe { Pin::new_unchecked(self.guard.deref()) } + } + + #[inline] + pub fn as_mut(&mut self) -> Pin<&mut ::T> + where + ::Guard<'a>: DerefMut, + { + // SAFETY: See PinnedGuard::as_ref() + unsafe { Pin::new_unchecked(self.guard.deref_mut()) } + } +} + +/// # Safety +/// +/// This trait can only be implemented for types that do not allow creating multiple locks that can +/// all be used to get an ``&mut T``. For example, ``&Mutex`` could not implement this trait, as +/// it is possible to just copy it, and such reference can then be used to gain access to a ``&mut +/// T``. +pub unsafe trait PinnableLock: Lock {} +unsafe impl PinnableLock for Mutex {} +unsafe impl PinnableLock for SpinLock {} + +pub struct PinnedLock { + inner: L, +} + +impl_from_contained!((L) PinnedLock, inner: L); + +impl PinnedLock where - >::Guard<'a>: DerefMut, - Self: 'a + Lock, + L: PinnableLock, { + #[inline] + pub fn new(lock: L) -> PinnedLock { + PinnedLock { inner: lock } + } + + #[inline] + pub fn lock<'a>(self: Pin<&'a Self>) -> PinnedGuard<'a, L> { + // SAFETY: As per PinnableLock guarantees, there is nothing else that can leak an &mut T, + // so we can safely create a PinnedGuard that will allow getting an Pin<&mut T> + PinnedGuard { + guard: self.get_ref().inner.lock(), + } + } } opaque_type!(struct CSpinLock, "spinlock_t", "linux/spinlock.h"); @@ -234,14 +300,17 @@ impl Drop for SpinLockGuard<'_, T> { } } -impl<'guard, T> LockGuard<'guard, T> for SpinLockGuard<'guard, T> {} +impl<'guard, T> LockGuard<'guard> for SpinLockGuard<'guard, T> { + type T = T; +} unsafe impl Sync for SpinLock {} unsafe impl Send for SpinLock {} -impl Lock for SpinLock { +impl Lock for SpinLock { + type T = T; type Guard<'a> - = SpinLockGuard<'a, T> + = SpinLockGuard<'a, Self::T> where Self: 'a; @@ -263,7 +332,7 @@ impl Lock for SpinLock { } } -impl<'a, T: 'a + Send> _LockMut<'a, T> for SpinLock {} +impl<'a, T: 'a + Send> _LockMut<'a> for SpinLock {} opaque_type!(pub struct CMutex, "struct mutex", "linux/mutex.h"); @@ -286,7 +355,7 @@ macro_rules! new_static_mutex { ($vis:vis $name:ident, $ty:ty, $data:expr) => { $vis static $name: $crate::runtime::sync::Mutex<$ty> = $crate::runtime::sync::Mutex::__internal_from_ref($data, { - #[$crate::inlinec::cstatic] + #[::lisakmod_macros::inlinec::cstatic] static STATIC_MUTEX: $crate::runtime::sync::CMutex = ( "#include ", "DEFINE_MUTEX(STATIC_VARIABLE);" @@ -366,6 +435,8 @@ impl Mutex { } } +impl_from_contained!((T) Mutex, data: T); + impl Drop for Mutex { #[inline] fn drop(&mut self) { @@ -425,14 +496,17 @@ impl Drop for MutexGuard<'_, T> { } } -impl<'guard, T> LockGuard<'guard, T> for MutexGuard<'guard, T> {} +impl<'guard, T> LockGuard<'guard> for MutexGuard<'guard, T> { + type T = T; +} unsafe impl Send for Mutex {} unsafe impl Sync for Mutex {} -impl Lock for Mutex { +impl Lock for Mutex { + type T = T; type Guard<'a> - = MutexGuard<'a, T> + = MutexGuard<'a, Self::T> where Self: 'a; @@ -452,7 +526,7 @@ impl Lock for Mutex { } } -impl<'a, T: 'a + Send> _LockMut<'a, T> for Mutex {} +impl<'a, T: 'a + Send> _LockMut<'a> for Mutex {} pub struct Rcu { // This pointer is actually a Box in disguise obtained with Box::into_raw(). We keep it as a @@ -534,7 +608,9 @@ pub struct RcuGuard<'a, T> { _phantom: PhantomData<&'a T>, } -impl<'guard, T> LockGuard<'guard, T> for RcuGuard<'guard, T> {} +impl<'guard, T> LockGuard<'guard> for RcuGuard<'guard, T> { + type T = T; +} impl Deref for RcuGuard<'_, T> { type Target = T; @@ -564,9 +640,10 @@ impl Drop for RcuGuard<'_, T> { unsafe impl Sync for Rcu {} unsafe impl Send for Rcu {} -impl Lock for Rcu { +impl Lock for Rcu { + type T = T; type Guard<'a> - = RcuGuard<'a, T> + = RcuGuard<'a, Self::T> where Self: 'a; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/synth_traceevent.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/synth_traceevent.rs new file mode 100644 index 0000000000000000000000000000000000000000..c1480b6ffe28517cc62a0e674b65f373dec4a879 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/synth_traceevent.rs @@ -0,0 +1,299 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +//! This module implements ftrace events bindings using the CONFIG_SYNTH_EVENTS interface, rather +//! than the traditional TRACE_EVENT() macro. + +use alloc::{ffi::CString, string::String, vec::Vec}; +use core::{ + cell::UnsafeCell, + ffi::{CStr, c_int, c_uchar}, +}; + +use lisakmod_macros::inlinec::{cfunc, incomplete_opaque_type, opaque_type}; + +use crate::{ + error::{Error, error}, +}; + +pub trait FieldTy { + const NAME: &'static str; + + fn field_desc(name: String) -> FieldDesc { + FieldDesc { + c_ty: Self::NAME.into(), + name, + } + } + + fn to_u64(self) -> u64; +} + +macro_rules! impl_field { + ($ty:ty, $c_name:literal, $to_u64:expr) => { + impl FieldTy for $ty { + const NAME: &'static str = $c_name; + + #[inline] + fn to_u64(self) -> u64 { + $to_u64(self) + } + } + }; +} + +impl_field!(u8, "u8", |x| x as u64); +impl_field!(i8, "s8", |x| x as u64); +impl_field!(u16, "u16", |x| x as u64); +impl_field!(i16, "s16", |x| x as u64); +impl_field!(u32, "u32", |x| x as u64); +impl_field!(i32, "s32", |x| x as u64); +impl_field!(u64, "u64", |x| x); +impl_field!(i64, "s64", |x| x as u64); +impl_field!(&CStr, "char[]", |x: &CStr| x.as_ptr() as usize as u64); + +pub struct FieldDesc { + name: String, + c_ty: String, +} + +struct Fields {} + +opaque_type!( + pub struct CEventFile, + "struct trace_event_file", + "linux/trace_events.h", +); + +incomplete_opaque_type!( + pub struct CSynthEvent, + "struct synth_event", + "linux/trace_events.h" +); + +opaque_type!( + struct CSynthFieldDesc, + "struct synth_field_desc", + "linux/trace_events.h", + + attr_accessors {name: *const c_uchar}, + attr_accessors {type as typ: *const c_uchar}, +); + +pub struct EventDesc { + // SAFETY: We need to hold onto those as they have been passed to the synthetic event kernel + // API. + name: CString, + c_field_descs: Vec<(CString, CString)>, + pub __c_event_file: *const UnsafeCell, + pub __c_synth_event: *const UnsafeCell, +} +unsafe impl Send for EventDesc {} + +impl EventDesc { + pub fn new(name: &str, field_descs: Vec) -> Result { + #[cfunc] + unsafe fn make_event<'a, 'b>( + name: &'a CStr, + fields: *mut CSynthFieldDesc, + len: usize, + ) -> Result<(), c_int> { + r#" + #include + #include + "#; + + r#" + return synth_event_create(name, fields, len, THIS_MODULE); + "# + } + + fn to_c_string(s: &str) -> CString { + CString::new(s) + .expect("Cannot convert Rust string to C string if it contains nul bytes") + } + + let c_field_descs: Vec<_> = field_descs + .iter() + .map(|desc| (to_c_string(&desc.name), to_c_string(&desc.c_ty))) + .collect(); + + let name = to_c_string(name); + { + let mut array: Vec = c_field_descs + .iter() + .map(|(name, ty)| { + unsafe { + CSynthFieldDesc::new_stack(move |this| { + this.name_raw_mut().write(name.as_ptr() as *const c_uchar); + this.typ_raw_mut().write(ty.as_ptr() as *const c_uchar); + Ok::<(), core::convert::Infallible>(()) + }) + } + .unwrap() + }) + .collect(); + + // SAFETY: make_event() will fail if an event already exists with that name, so once we + // have successfully created the event, we can rely on any by-name lookup in various + // kernel APIs to always give back data related to what we allocated here. + + // SAFETY: we will hold onto all the CString we created here until we call + // delete_event(), at which point the kernel will not need them anymore. + unsafe { make_event(name.as_c_str(), array.as_mut_ptr(), array.len()) }.map_err( + |code| error!("Synthetic ftrace event {name:?} registration failed: {code}"), + )? + } + + #[cfunc] + unsafe fn synth_event_find(name: &CStr) -> *const UnsafeCell { + r#" + #include + #include "introspection.h" + "#; + + r#" + #if HAS_SYMBOL(synth_event_find) + return synth_event_find(name); + #else + return NULL; + #endif + "# + } + + let __c_synth_event = unsafe { synth_event_find(name.as_c_str()) }; + + #[cfunc] + unsafe fn trace_get_event_file<'a, 'b>( + name: &'a CStr, + ) -> Option<&'b UnsafeCell> { + r#" + #include + "#; + + r#" + return trace_get_event_file(NULL, "synthetic", name); + "# + } + let __c_event_file = unsafe { trace_get_event_file(name.as_c_str()) } + .ok_or_else(|| error!("Could not get trace_event_file for synthetic event {name:?}"))?; + + Ok(EventDesc { + name, + c_field_descs, + __c_event_file, + __c_synth_event, + }) + } + + /// # Safety + /// + /// The passed `vals` must be consistent with the event format + #[inline] + pub unsafe fn __trace(&self, vals: &mut [u64]) { + #[cfunc] + unsafe fn trace_synth( + event_file: *const UnsafeCell, + synth_event: *const UnsafeCell, + vals: *mut u64, + n_vals: usize, + ) -> Result<(), c_int> { + r#" + #include + #include "introspection.h" + "#; + + r#" + // As of 6.13, there is no way to emit the event in all instances where it was enabled, only in + // the top-level buffer. This may get solved if the patches mentioned there are merged: + // https://bugzilla.kernel.org/show_bug.cgi?id=219876 + #if HAS_SYMBOL(synth_event_trace2) + static unsigned int var_ref_idx[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + }; + BUG_ON(!synth_event); + BUG_ON(n_vals > ARRAY_SIZE(var_ref_idx)); + synth_event_trace2(synth_event, vals, var_ref_idx); + return 0; + #elif HAS_SYMBOL(synth_event_trace_array) + BUG_ON(!event_file); + return synth_event_trace_array(event_file, vals, n_vals); + #elif !defined(CONFIG_SYNTH_EVENTS) + # error "CONFIG_SYNTH_EVENTS=y is necessary" + #endif + "# + } + unsafe { + trace_synth( + self.__c_event_file, + self.__c_synth_event, + vals.as_mut_ptr(), + vals.len(), + ) + } + .expect("Could not emit synthetic ftrace event"); + } +} + +impl Drop for EventDesc { + fn drop(&mut self) { + #[cfunc] + unsafe fn trace_put_event_file<'a>(event_file: *const UnsafeCell) { + r#" + #include + #include "utils.h" + "#; + + r#" + return trace_put_event_file(CONST_CAST(struct trace_event_file *, event_file)); + "# + } + + #[cfunc] + unsafe fn delete_event(name: &CStr) -> Result<(), c_int> { + r#" + #include + "#; + + r#" + return synth_event_delete(name); + "# + } + + // SAFETY: CEventFile is valid until the event gets deleted, which hasn't happened yet. + unsafe { trace_put_event_file(self.__c_event_file) } + + // This may fail if the kernel in use exhibits this problem: + // https://bugzilla.kernel.org/show_bug.cgi?id=219875 + unsafe { delete_event(self.name.as_c_str()) }.expect("Could not delete synthetic event"); + } +} + +macro_rules! new_event { + ($name:ident, fields: {$($field_name:ident: $field_ty:ty),* $(,)?}) => { + { + $crate::runtime::traceevent::EventDesc::new( + stringify!($name), + ::alloc::vec![ + $( + <$field_ty as $crate::runtime::traceevent::FieldTy>::field_desc(stringify!($field_name).into()) + ),* + ], + ).map(|event_desc| move |$($field_name: $field_ty),*| { + let mut vals = [ + $( + <$field_ty as $crate::runtime::traceevent::FieldTy>::to_u64($field_name) + ),* + ]; + // SAFETY: The event fields type and order is guaranteed to be consistent between + // the defintion and the use of the array, as they are both created here in the + // new_event!() macro in the same order. + unsafe { + event_desc.__trace(&mut vals) + } + }) + } + }; +} +pub(crate) use new_event; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sysfs.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sysfs.rs index 7726673e64dd85c872061181621412d51a2ad77b..6dcdb963feb59a41d87434a197e363968d0460c8 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sysfs.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sysfs.rs @@ -10,10 +10,9 @@ use core::{ pin::Pin, }; -use crate::{ - inlinec::{c_eval, cexport, cfunc, opaque_type}, - misc::{container_of, mut_container_of}, -}; +use lisakmod_macros::inlinec::{c_eval, cexport, cfunc, opaque_type}; + +use crate::mem::{container_of, mut_container_of}; opaque_type!( struct CKObj, diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/traceevent.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/traceevent.rs index a4d0d8539f5774bd1bd70c279cba5abf6919dedb..ead73e5809c6059f6812d6fbaf56c382df7a1ef8 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/traceevent.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/traceevent.rs @@ -1,293 +1,81 @@ /* SPDX-License-Identifier: GPL-2.0 */ -use alloc::{ffi::CString, string::String, vec::Vec}; -use core::{ - cell::UnsafeCell, - ffi::{CStr, c_int, c_uchar}, -}; +use core::ffi::CStr; -use crate::{ - error::{Error, error}, - inlinec::{cfunc, opaque_type}, -}; +use lisakmod_macros::inlinec::IntoFfi; -pub trait FieldTy { +pub trait FieldTy: IntoFfi { const NAME: &'static str; - - fn field_desc(name: String) -> FieldDesc { - FieldDesc { - c_ty: Self::NAME.into(), - name, - } - } - - fn to_u64(self) -> u64; } macro_rules! impl_field { - ($ty:ty, $c_name:literal, $to_u64:expr) => { + ($ty:ty, $c_name:literal) => { impl FieldTy for $ty { const NAME: &'static str = $c_name; - - #[inline] - fn to_u64(self) -> u64 { - $to_u64(self) - } } }; } -impl_field!(u8, "u8", |x| x as u64); -impl_field!(i8, "s8", |x| x as u64); -impl_field!(u16, "u16", |x| x as u64); -impl_field!(i16, "s16", |x| x as u64); -impl_field!(u32, "u32", |x| x as u64); -impl_field!(i32, "s32", |x| x as u64); -impl_field!(u64, "u64", |x| x); -impl_field!(i64, "s64", |x| x as u64); -impl_field!(&CStr, "char[]", |x: &CStr| x.as_ptr() as usize as u64); - -pub struct FieldDesc { - name: String, - c_ty: String, -} - -struct Fields {} - -opaque_type!( - pub struct CEventFile, - "struct trace_event_file", - "linux/trace_events.h", -); - -// Since "struct synth_event" is an incomplete type, we cannot use opaque_type!() as sizeof(struct -// synth_event) is invalid -type CSynthEvent = core::ffi::c_void; - -opaque_type!( - struct CSynthFieldDesc, - "struct synth_field_desc", - "linux/trace_events.h", - - attr_accessors {name: *const c_uchar}, - attr_accessors {type as typ: *const c_uchar}, -); - -pub struct EventDesc { - // SAFETY: We need to hold onto those as they have been passed to the synthetic event kernel - // API. - name: CString, - c_field_descs: Vec<(CString, CString)>, - pub __c_event_file: *const UnsafeCell, - pub __c_synth_event: *const UnsafeCell, -} -unsafe impl Send for EventDesc {} +// New implementations of FieldTy should get a matching implementations in the process_rust.py +// script. +impl_field!(u8, "u8"); +impl_field!(i8, "s8"); +impl_field!(u16, "u16"); +impl_field!(i16, "s16"); +impl_field!(u32, "u32"); +impl_field!(i32, "s32"); +impl_field!(u64, "u64"); +impl_field!(i64, "s64"); +impl_field!(&CStr, "string"); -impl EventDesc { - pub fn new(name: &str, field_descs: Vec) -> Result { - #[cfunc] - unsafe fn make_event<'a, 'b>( - name: &'a CStr, - fields: *mut CSynthFieldDesc, - len: usize, - ) -> Result<(), c_int> { - r#" - #include - #include - "#; - - r#" - return synth_event_create(name, fields, len, THIS_MODULE); - "# - } - - fn to_c_string(s: &str) -> CString { - CString::new(s) - .expect("Cannot convert Rust string to C string if it contains nul bytes") - } - - let c_field_descs: Vec<_> = field_descs - .iter() - .map(|desc| (to_c_string(&desc.name), to_c_string(&desc.c_ty))) - .collect(); - - let name = to_c_string(name); - { - let mut array: Vec = c_field_descs - .iter() - .map(|(name, ty)| { - unsafe { - CSynthFieldDesc::new_stack(move |this| { - this.name_raw_mut().write(name.as_ptr() as *const c_uchar); - this.typ_raw_mut().write(ty.as_ptr() as *const c_uchar); - Ok::<(), core::convert::Infallible>(()) - }) +macro_rules! new_event { + ($name:ident, fields: {$($field_name:ident: $field_ty:ty),* $(,)?}) => {{ + ::lisakmod_macros::misc::json_metadata!({ + "type": "define-ftrace-event", + "name": (::core::stringify!($name)), + "fields": [ + $( + { + "name": (::core::stringify!($field_name)), + "logical-type": (<$field_ty as $crate::runtime::traceevent::FieldTy>::NAME), + "c-arg-type": (<$field_ty as ::lisakmod_macros::inlinec::FfiType>::C_TYPE) } - .unwrap() - }) - .collect(); - - // SAFETY: make_event() will fail if an event already exists with that name, so once we - // have successfully created the event, we can rely on any by-name lookup in various - // kernel APIs to always give back data related to what we allocated here. - - // SAFETY: we will hold onto all the CString we created here until we call - // delete_event(), at which point the kernel will not need them anymore. - unsafe { make_event(name.as_c_str(), array.as_mut_ptr(), array.len()) }.map_err( - |code| error!("Synthetic ftrace event {name:?} registration failed: {code}"), - )? - } - - #[cfunc] - unsafe fn synth_event_find(name: &CStr) -> *const UnsafeCell { - r#" - #include - #include "introspection.h" - "#; + ),* + ] + }); - r#" - #if HAS_SYMBOL(synth_event_find) - return synth_event_find(name); - #else - return NULL; - #endif - "# - } - - let __c_synth_event = unsafe { synth_event_find(name.as_c_str()) }; - - #[cfunc] - unsafe fn trace_get_event_file<'a, 'b>( - name: &'a CStr, - ) -> Option<&'b UnsafeCell> { - r#" - #include - "#; - - r#" - return trace_get_event_file(NULL, "synthetic", name); - "# - } - let __c_event_file = unsafe { trace_get_event_file(name.as_c_str()) } - .ok_or_else(|| error!("Could not get trace_event_file for synthetic event {name:?}"))?; - - Ok(EventDesc { - name, - c_field_descs, - __c_event_file, - __c_synth_event, - }) - } - - /// # Safety - /// - /// The passed `vals` must be consistent with the event format - #[inline] - pub unsafe fn __trace(&self, vals: &mut [u64]) { - #[cfunc] - unsafe fn trace_synth( - event_file: *const UnsafeCell, - synth_event: *const UnsafeCell, - vals: *mut u64, - n_vals: usize, - ) -> Result<(), c_int> { - r#" - #include - #include "introspection.h" - "#; - - r#" - // As of 6.13, there is no way to emit the event in all instances where it was enabled, only in - // the top-level buffer. This may get solved if the patches mentioned there are merged: - // https://bugzilla.kernel.org/show_bug.cgi?id=219876 - #if HAS_SYMBOL(synth_event_trace2) - static unsigned int var_ref_idx[] = { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, - 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, - 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, - }; - BUG_ON(!synth_event); - BUG_ON(n_vals > ARRAY_SIZE(var_ref_idx)); - synth_event_trace2(synth_event, vals, var_ref_idx); - return 0; - #elif HAS_SYMBOL(synth_event_trace_array) - BUG_ON(!event_file); - return synth_event_trace_array(event_file, vals, n_vals); - #elif !defined(CONFIG_SYNTH_EVENTS) - # error "CONFIG_SYNTH_EVENTS=y is necessary" - #endif - "# - } - unsafe { - trace_synth( - self.__c_event_file, - self.__c_synth_event, - vals.as_mut_ptr(), - vals.len(), - ) - } - .expect("Could not emit synthetic ftrace event"); - } -} - -impl Drop for EventDesc { - fn drop(&mut self) { - #[cfunc] - unsafe fn trace_put_event_file<'a>(event_file: *const UnsafeCell) { - r#" - #include - #include "utils.h" - "#; - - r#" - return trace_put_event_file(CONST_CAST(struct trace_event_file *, event_file)); - "# - } - - #[cfunc] - unsafe fn delete_event(name: &CStr) -> Result<(), c_int> { - r#" - #include - "#; - - r#" - return synth_event_delete(name); - "# - } - - // SAFETY: CEventFile is valid until the event gets deleted, which hasn't happened yet. - unsafe { trace_put_event_file(self.__c_event_file) } - - // This may fail if the kernel in use exhibits this problem: - // https://bugzilla.kernel.org/show_bug.cgi?id=219875 - unsafe { delete_event(self.name.as_c_str()) }.expect("Could not delete synthetic event"); - } -} - -macro_rules! new_event { - ($name:ident, fields: {$($field_name:ident: $field_ty:ty),* $(,)?}) => { { - $crate::runtime::traceevent::EventDesc::new( - stringify!($name), - ::alloc::vec![ - $( - <$field_ty as $crate::runtime::traceevent::FieldTy>::field_desc(stringify!($field_name).into()) - ),* - ], - ).map(|event_desc| move |$($field_name: $field_ty),*| { - let mut vals = [ - $( - <$field_ty as $crate::runtime::traceevent::FieldTy>::to_u64($field_name) - ),* - ]; - // SAFETY: The event fields type and order is guaranteed to be consistent between - // the defintion and the use of the array, as they are both created here in the - // new_event!() macro in the same order. - unsafe { - event_desc.__trace(&mut vals) + #[::lisakmod_macros::inlinec::cfunc] + fn emit($($field_name: $field_ty),*) { + r#" + #include "ftrace_events.h" + "#; + + // Call the trace_foo() function created by the TRACE_EVENT(foo, ...) kernel macro + ::core::concat!( + "trace_", ::core::stringify!($name), "(", + $crate::misc::join!( + ", ", + $(::core::stringify!($field_name)),* + ), + ");" + ); + } + + // Wrap the function in a closure so that we can later on switch to a dynamic event + // creation facility if it becomes available. This is achieved by: + // 1. Returning a closure rather than the fn item, so client code is used to dealing + // with a closure. + // 2. Make the closure !Copy, so that client code does not accidentally relies on that. + struct NonCopy; + let x = NonCopy; + Ok::<_, $crate::error::Error>( + move |$($field_name: $field_ty),*| { + let _ = &x; + emit($($field_name),*) } - }) + ) } - }; + }}; } pub(crate) use new_event; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/tracepoint.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/tracepoint.rs index 5ecf09978aabce436a6d37177b2e3a5512737622..190edb9f27658565f0c5cc27a6d4c92ee840b8d9 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/tracepoint.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/tracepoint.rs @@ -1,17 +1,19 @@ /* SPDX-License-Identifier: GPL-2.0 */ -use alloc::{ffi::CString, sync::Arc, vec::Vec}; +use alloc::{boxed::Box, ffi::CString, vec::Vec}; use core::{ any::Any, cell::UnsafeCell, ffi::{CStr, c_int, c_void}, fmt, marker::PhantomData, + pin::Pin, }; +use lisakmod_macros::inlinec::{cfunc, opaque_type}; + use crate::{ error::{Error, error}, - inlinec::{cfunc, opaque_type}, runtime::{ printk::pr_debug, sync::{Lock as _, LockdepClass, Mutex}, @@ -24,20 +26,20 @@ opaque_type!( "linux/tracepoint.h", ); -pub struct Tracepoint<'tp, F> { +pub struct Tracepoint<'tp, Args> { // struct tracepoint API allows mutation but protects it with an internal mutex // (tracepoint_mutex static variable in tracepoint.c) c_tp: &'tp UnsafeCell, - _phantom: PhantomData, + _phantom: PhantomData, } // SAFETY: The Tracepoint is safe to pass around as it only stores shared references. -unsafe impl<'tp, F> Send for Tracepoint<'tp, F> {} +unsafe impl<'tp, Args> Send for Tracepoint<'tp, Args> {} // SAFETY: the tracepoint kernel API is protected with "tracepoints_mutex" mutex (see // tracepoint.c), so it safe to call it from multiple threads. -unsafe impl<'tp, F> Sync for Tracepoint<'tp, F> {} +unsafe impl<'tp, Args> Sync for Tracepoint<'tp, Args> {} -impl Tracepoint<'static, F> { +impl Tracepoint<'static, Args> { /// # Safety /// /// The tracepoint being looked up must be defined in the base kernel image, not in a module. @@ -96,7 +98,7 @@ pub enum TracepointError { Kernel(c_int), } -impl<'tp, F> Tracepoint<'tp, F> { +impl<'tp, Args> Tracepoint<'tp, Args> { // We cannot use the facilities of opaque_type!() as they require a &Self, which we cannot // safely materialize for CTracepoint as there may be concurrent users of the struct. pub fn name<'a>(&'a self) -> &'a str { @@ -115,8 +117,8 @@ impl<'tp, F> Tracepoint<'tp, F> { pub fn register_probe<'probe>( &'probe self, - probe: &'probe Probe<'probe, F>, - ) -> Result, Error> + probe: &'probe Probe<'probe, Args>, + ) -> Result, Error> where 'tp: 'probe, { @@ -127,34 +129,28 @@ impl<'tp, F> Tracepoint<'tp, F> { #[cfunc] unsafe fn register( tp: *mut CTracepoint, - probe: *mut c_void, - data: *mut c_void, + probe: *const c_void, + data: *const c_void, ) -> Result<(), c_int> { r#" #include "#; r#" - return tracepoint_probe_register(tp, probe, data); + return tracepoint_probe_register(tp, CONST_CAST(void *, probe), CONST_CAST(void *, data)); "# } // SAFETY: If the Probe is alive, then its closure is alive too. Probe will be kept alive // until the last RegisteredProbe is dropped, at which point we known that // tracepoint_probe_unregister() has been called. - unsafe { - register( - self.c_tp.get(), - probe.probe as *mut c_void, - probe.closure as *mut c_void, - ) - } - .map_err(|code| error!("Tracepoint probe registration failed: {code}"))?; + unsafe { register(self.c_tp.get(), probe.probe, probe.closure) } + .map_err(|code| error!("Tracepoint probe registration failed: {code}"))?; Ok(RegisteredProbe { probe, tp: self }) } } -impl<'tp, F> fmt::Debug for Tracepoint<'tp, F> { +impl<'tp, Args> fmt::Debug for Tracepoint<'tp, Args> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { f.debug_struct("Tracepoint") .field("name", &self.name()) @@ -162,12 +158,12 @@ impl<'tp, F> fmt::Debug for Tracepoint<'tp, F> { } } -pub struct RegisteredProbe<'probe, F> { - probe: &'probe Probe<'probe, F>, - tp: &'probe Tracepoint<'probe, F>, +pub struct RegisteredProbe<'probe, Args> { + probe: &'probe Probe<'probe, Args>, + tp: &'probe Tracepoint<'probe, Args>, } -impl<'probe, F> Drop for RegisteredProbe<'probe, F> { +impl<'probe, Args> Drop for RegisteredProbe<'probe, Args> { fn drop(&mut self) { /// # Safety /// @@ -176,29 +172,23 @@ impl<'probe, F> Drop for RegisteredProbe<'probe, F> { #[cfunc] unsafe fn unregister( tp: *mut CTracepoint, - probe: *mut c_void, - data: *mut c_void, + probe: *const c_void, + data: *const c_void, ) -> Result<(), c_int> { r#" #include "#; r#" - return tracepoint_probe_unregister(tp, probe, data); + return tracepoint_probe_unregister(tp, CONST_CAST(void *, probe), CONST_CAST(void *, data)); "# } let probe = self.probe; // SAFETY: Since we only create RegisteredProbe values when the registration succeeded, we // ensure that this will be the only matching call to tracepoint_probe_unregister() - unsafe { - unregister( - self.tp.c_tp.get(), - probe.probe as *mut c_void, - probe.closure as *mut c_void, - ) - } - .expect("Failed to unregister tracepoint probe"); + unsafe { unregister(self.tp.c_tp.get(), probe.probe, probe.closure as *mut _) } + .expect("Failed to unregister tracepoint probe"); pr_debug!( "Called tracepoint_probe_unregister() for a probe attached to {:?}", @@ -208,7 +198,7 @@ impl<'probe, F> Drop for RegisteredProbe<'probe, F> { } pub struct ProbeDropper { - droppers: Mutex>>>, + droppers: Mutex>>>>, } impl fmt::Debug for ProbeDropper { @@ -263,32 +253,35 @@ impl Drop for ProbeDropper { } } -pub struct Probe<'probe, F> { - closure: *const (), - probe: *mut (), - _phantom: PhantomData<&'probe F>, +pub struct Probe<'probe, Args> { + closure: *const c_void, + probe: *const c_void, + _phantom: PhantomData<(&'probe (), Args)>, } -impl<'probe, F> Probe<'probe, F> { +impl<'probe, Args> Probe<'probe, Args> { /// # Safety /// /// The `probe` must be compatible with the [`Closure`] type in use. #[inline] pub unsafe fn __private_new( - closure: Arc, - probe: *mut (), + closure: Pin>, + probe: *const c_void, + // SAFETY: we borrow ProbeDropper for &'probe, which ensures the ProbeDropper will not be + // dropped before Probe<'probe, _> dropper: &'probe ProbeDropper, - ) -> Probe<'probe, F> + ) -> Probe<'probe, Args> where Closure: 'static + Send + Sync, { - let ptr = Arc::as_ptr(&closure) as *const (); + let ptr: &Closure = &closure; + let ptr: *const Closure = ptr; dropper.droppers.lock().push(Some(closure)); // SAFETY: the probe and closure pointer we store here are guaranteed to be valid for the // lifetime of the Probe object, as we borrow the ProbeDropper for that duration. Probe { - closure: ptr, + closure: ptr as *const c_void, probe, _phantom: PhantomData, } @@ -296,9 +289,9 @@ impl<'probe, F> Probe<'probe, F> { } // SAFETY: this is ok as the closure we store is also Send -unsafe impl<'probe, F> Send for Probe<'probe, F> {} +unsafe impl<'probe, Args> Send for Probe<'probe, Args> {} // SAFETY: this is ok as the closure we store is also Sync -unsafe impl<'probe, F> Sync for Probe<'probe, F> {} +unsafe impl<'probe, Args> Sync for Probe<'probe, Args> {} macro_rules! new_probe { ($dropper:expr, ( $($arg_name:ident: $arg_ty:ty),* ) $body:block) => { @@ -307,13 +300,13 @@ macro_rules! new_probe { // soundly implement Send and Sync type Closure = impl Fn($($arg_ty),*) + ::core::marker::Send + ::core::marker::Sync + 'static; - let closure: ::alloc::sync::Arc = ::alloc::sync::Arc::new( + let closure: ::core::pin::Pin<::alloc::boxed::Box> = ::alloc::boxed::Box::pin( move |$($arg_name: $arg_ty),*| { $body } ); - #[$crate::inlinec::cexport] + #[::lisakmod_macros::inlinec::cexport] fn probe(closure: *const c_void, $($arg_name: $arg_ty),*) { let closure = closure as *const Closure; // SAFETY: Since we call tracepoint_probe_unregister() in ::__private_new( + $crate::runtime::tracepoint::Probe::<( $($arg_ty),* )>::__private_new( closure, - probe as *mut (), + probe as *const c_void, $dropper, ) } } } } + +#[allow(unused_imports)] pub(crate) use new_probe; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/wq.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/wq.rs new file mode 100644 index 0000000000000000000000000000000000000000..cbaaefc4cb8a407a49d736cb09a2bd1444271f10 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/wq.rs @@ -0,0 +1,356 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +use alloc::boxed::Box; +use core::{ + cell::UnsafeCell, + convert::{AsMut, AsRef, Infallible}, + ffi::{CStr, c_int, c_void}, + pin::Pin, + ptr::NonNull, +}; + +use lisakmod_macros::inlinec::{cfunc, incomplete_opaque_type, opaque_type}; +use pin_project::{pin_project, pinned_drop}; + +use crate::{ + mem::{FromContained, impl_from_contained}, + runtime::sync::{Lock as _, LockdepClass, Mutex, PinnedLock}, +}; + +incomplete_opaque_type!( + struct CWq, + "struct workqueue_struct", + "linux/workqueue.h" +); + +#[derive(Debug)] +pub struct Wq { + c_wq: NonNull>, +} +unsafe impl Send for Wq {} +unsafe impl Sync for Wq {} + +impl Default for Wq { + fn default() -> Self { + Self::new() + } +} + +impl Wq { + #[inline] + pub fn new() -> Wq { + #[cfunc] + fn allo_workqueue(name: &CStr) -> Option>> { + r#" + #include + "#; + + r#" + return alloc_workqueue(name, WQ_FREEZABLE, 0); + "#; + } + + let c_wq = allo_workqueue(c"lisa").expect("Unable to allocate struct workqueue_struct"); + Wq { c_wq } + } + + #[inline] + fn c_wq(&self) -> &UnsafeCell { + unsafe { self.c_wq.as_ref() } + } +} + +impl Drop for Wq { + fn drop(&mut self) { + #[cfunc] + fn destroy_workqueue(wq: NonNull>) { + r#" + #include + "#; + + r#" + destroy_workqueue(wq); + "#; + } + destroy_workqueue(self.c_wq); + } +} + +opaque_type!( + pub struct CDelayedWork, + "struct delayed_work", + "linux/workqueue.h" +); + +#[pin_project(PinnedDrop)] +pub struct DelayedWork<'wq> { + #[pin] + c_dwork: CDelayedWork, + wq: &'wq Wq, +} +// SAFETY: DelayedWork is Send but not Sync as struct delayed_work is not Sync. Some APIs like +// queue_delayed_work() seem to be callable from any thread, but this is only partly true: the +// workqueue is designed for concurrent access, but the delayed_work itself is not protected +// against concurrent updates. The assumption of the kernel API is that only one thread will +// manipulate the delayed_work at a time. +unsafe impl<'wq> Send for DelayedWork<'wq> {} + +impl_from_contained!(('wq) DelayedWork<'wq>, c_dwork: CDelayedWork); + +impl<'wq> DelayedWork<'wq> { + #[inline] + fn enqueue(self: Pin<&mut Self>, delay: u64) { + #[cfunc] + fn enqueue( + wq: &UnsafeCell, + dwork: Pin<&mut CDelayedWork>, + delay: u64, + ) -> Result<(), c_int> { + r#" + #include + #include + "#; + + r#" + queue_delayed_work(wq, dwork, usecs_to_jiffies(delay)); + return 0; + "#; + } + let this = self.project(); + enqueue(this.wq.c_wq(), this.c_dwork, delay) + .expect("Could not initialize workqueue's delayed work"); + } + + #[inline] + fn cancel_sync(self: Pin<&mut Self>) { + #[cfunc] + fn cancel_delayed_work_sync(dwork: Pin<&mut CDelayedWork>) { + r#" + #include + "#; + + r#" + cancel_delayed_work_sync(dwork); + "# + } + cancel_delayed_work_sync(self.project().c_dwork) + } +} + +#[pinned_drop] +impl<'wq> PinnedDrop for DelayedWork<'wq> { + #[inline] + fn drop(self: Pin<&mut Self>) { + // SAFETY: We need to ensure the worker will not fire anymore and has finished running + // before we return, so we don't accidentally free the closure while it is in use. + self.cancel_sync(); + } +} + +#[pin_project] +pub struct WorkItemInner<'wq, F: 'wq + Send + Sync> { + // SAFETY: WorkItemInner _must_ be pinned as the address of __dwork will be passed around C + // APIs. + #[pin] + pub __dwork: DelayedWork<'wq>, + pub __f: F, +} + +impl_from_contained!(('wq, F: Send + Sync) WorkItemInner<'wq, F>, __dwork: DelayedWork<'wq>); + +impl<'wq, F> WorkItemInner<'wq, F> +where + F: Send + Sync, +{ + #[inline] + fn new(wq: &'wq Wq, f: F) -> Pin>> + where + F: Worker, + { + #[cfunc] + unsafe fn init_dwork( + wq: &UnsafeCell, + dwork: Pin<&mut CDelayedWork>, + worker: *const c_void, + ) -> Result<(), c_int> { + r#" + #include + "#; + + r#" + INIT_DELAYED_WORK(dwork, worker); + return 0; + "#; + } + // SAFETY: CDelayedWork does not have any specific validity invariant since it's + // essentially an opaque type. We don't want to pass it to the C API before it is moved to + // its final memory location in a Box. + let c_dwork = unsafe { CDelayedWork::new_stack(|_| Ok::<(), Infallible>(())) }.unwrap(); + let __dwork = DelayedWork { c_dwork, wq }; + let new = Box::pin(PinnedLock::new(Mutex::new( + WorkItemInner { __dwork, __f: f }, + LockdepClass::new(), + ))); + + unsafe { + init_dwork( + wq.c_wq(), + new.as_ref() + .lock() + .as_mut() + .project() + .__dwork + .project() + .c_dwork, + F::worker() as *const c_void, + ) + } + .expect("Could not initialize workqueue's delayed work"); + + new + } + + #[inline] + unsafe fn from_dwork(c_dwork: Pin<&CDelayedWork>) -> Pin<&PinnedWorkItemInner<'wq, F>> { + unsafe { + let dwork = DelayedWork::<'wq>::from_contained(c_dwork.get_ref()) + .as_ref() + .unwrap(); + let inner = WorkItemInner::<'wq, F>::from_contained(dwork) + .as_ref() + .unwrap(); + let inner = Mutex::>::from_contained(inner) + .as_ref() + .unwrap(); + let inner = PinnedLock::>>::from_contained(inner) + .as_ref() + .unwrap(); + Pin::new_unchecked(inner) + } + } +} + +type PinnedWorkItemInner<'wq, F> = PinnedLock>>; + +pub struct WorkItem<'wq, F> +where + F: Send + Sync, +{ + // We need a Mutex as the queue_delayed_work() function protects the workqueue but not the the + // dwork itself. If multiple threads try to enqueue the same dwork at the same time, or if a + // thread tries to enqueue it at the same time as it enqueues itself, there would be a race + // condition. + inner: Pin>>, +} + +impl<'wq, F> WorkItem<'wq, F> +where + F: Fn(&mut dyn AbstractWorkItem) + Send + Sync + Worker, +{ + #[inline] + pub fn __private_new(wq: &'wq Wq, f: F) -> WorkItem<'wq, F> { + WorkItem { + inner: WorkItemInner::new(wq, f), + } + } + + #[inline] + pub fn enqueue(&self, delay: u64) { + self.inner + .as_ref() + .lock() + .as_mut() + .project() + .__dwork + .enqueue(delay) + } + + #[inline] + fn cancel_sync(self: Pin<&mut Self>) { + self.inner + .as_ref() + .lock() + .as_mut() + .project() + .__dwork + .cancel_sync() + } +} + +pub trait AbstractWorkItem { + fn enqueue(&mut self, delay: u64); +} + +impl<'wq> AbstractWorkItem for Pin<&mut DelayedWork<'wq>> { + #[inline] + fn enqueue(&mut self, delay: u64) { + self.as_mut().enqueue(delay) + } +} + +incomplete_opaque_type!( + pub struct __CWorkStruct, + "struct work_struct", + "linux/workqueue.h" +); + +impl __CWorkStruct { + pub unsafe fn __to_work_item<'wq, F>(self: Pin<&Self>) -> Pin<&PinnedWorkItemInner<'wq, F>> + where + F: Send + Sync, + { + #[cfunc] + fn to_dwork(work: Pin<&__CWorkStruct>) -> Pin<&CDelayedWork> { + r#" + #include + "#; + + r#" + return to_delayed_work(CONST_CAST(struct work_struct *, work)); + "# + } + let c_dwork = to_dwork(self); + unsafe { WorkItemInner::<'wq, F>::from_dwork(c_dwork) } + } +} + +pub unsafe trait Worker { + fn worker() -> unsafe extern "C" fn(*mut __CWorkStruct); +} + +macro_rules! new_work_item { + ($wq:expr, $f:expr) => {{ + // SAFETY: We need to ensure Send and Sync for the closure, as WorkItem relies on that + // to soundly implement Send and Sync + pub type Closure = impl Fn(&mut dyn $crate::runtime::wq::AbstractWorkItem) + + ::core::marker::Send + + ::core::marker::Sync + + 'static; + let closure: Closure = $f; + + unsafe impl $crate::runtime::wq::Worker for Closure { + fn worker() -> unsafe extern "C" fn(*mut $crate::runtime::wq::__CWorkStruct) { + #[::lisakmod_macros::inlinec::cexport] + pub unsafe fn worker(c_work_struct: *mut $crate::runtime::wq::__CWorkStruct) { + // The prototype of the exported function must be exactly as the C API expects, + // otherwise we get a CFI violation. However, we know we are actually passed a + // Pin<&__CWorkStruct> + let c_work_struct = unsafe { + ::core::pin::Pin::new_unchecked( + c_work_struct.as_ref().expect("Unexpected NULL pointer"), + ) + }; + let inner = unsafe { c_work_struct.__to_work_item::() }; + let mut inner = inner.lock(); + let mut inner = inner.as_mut().project(); + let abstr: &mut dyn $crate::runtime::wq::AbstractWorkItem = &mut inner.__dwork; + (inner.__f)(abstr); + } + worker + } + } + + $crate::runtime::wq::WorkItem::__private_new($wq, closure) + }}; +} +pub(crate) use new_work_item; diff --git a/lisa/_assets/kmodules/lisa/rust_exported_symbols.py b/lisa/_assets/kmodules/lisa/rust_exported_symbols.py deleted file mode 100755 index 76ed68af49164fa0b8f4f97210e1b1b63b9f6e1b..0000000000000000000000000000000000000000 --- a/lisa/_assets/kmodules/lisa/rust_exported_symbols.py +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env python3 -# -# SPDX-License-Identifier: Apache-2.0 -# -# Copyright (C) 2023, Arm Limited and contributors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -import argparse -from pathlib import Path -from shlex import quote -import subprocess -import os -import textwrap - - -def main(): - parser = argparse.ArgumentParser(""" - Get the list of exported Rust functions to make it available to C code (and - not garbage collect these entry points it when linking). - """) - - parser.add_argument('--rust-object', help='Built Rust object file', required=True) - parser.add_argument('--out-symbols-plain', help='File to write the symbol list, one per line') - parser.add_argument('--out-symbols-cli', help='File to write the symbol list as ld CLI --undefined options') - parser.add_argument('--out-start-stop-lds', help='File to write the linker script to provide __start_SECNAME and __stop_SECNAME symbols, which are not provided by Kbuild since the module is never linked into an executable or DSO') - - args = parser.parse_args() - - nm = os.environ.get('NM', 'nm') - out = subprocess.check_output( - [nm, '-gj', args.rust_object], - ) - all_symbols = sorted(set(out.decode().split())) - - def parse(sym, prefix): - if sym.startswith(prefix): - sym = sym[len(prefix):] - return sym - else: - return None - - # For each symbol we want to export in Rust, we create a companion symbol - # with a prefix that we pick up here. There unfortunately seems to be no - # cleaner way to convey the list of exported symbols from Rust code as of - # 2024. - user_symbols = [ - sym - for _sym in all_symbols - if (sym := parse(_sym, '__export_rust_symbol_')) - ] - - symbols = sorted(user_symbols) - - sep = '\n ' - print(f'Found exported symbols:{sep}{sep.join(symbols)}') - - if (path := args.out_symbols_plain): - content = '\n'.join(symbols) + '\n' - Path(path).write_text(content) - - if (path := args.out_symbols_cli): - content = ' '.join(( - f'--undefined {quote(sym)}' - for sym in symbols - )) + '\n' - Path(path).write_text(content) - - if (path := args.out_start_stop_lds): - sections = sorted({ - section - for sym in all_symbols - if ( - (section := parse(sym, '__start_')) or - (section := parse(sym, '__stop_')) - ) - }) - print(f'Found sections to generate __start_SECNAME and __stop_SECNAME symbols for:{sep}{sep.join(sections)}') - - def make_lds(section): - return textwrap.dedent(f''' - SECTIONS {{ - {section} : {{ - PROVIDE(__start_{section} = .); - KEEP(*({section})); - PROVIDE(__stop_{section} = .); - }} - }} - ''') - - lds = '\n'.join(map(make_lds, sections)) + '\n' - Path(path).write_text(lds) - -if __name__ == '__main__': - main() diff --git a/lisa/_assets/kmodules/lisa/utils.h b/lisa/_assets/kmodules/lisa/utils.h index 4a55d3e4137022cb91145b8641ce5028c21994a8..acd5c97209d850e7d9362ce3ebf81343fda3b588 100644 --- a/lisa/_assets/kmodules/lisa/utils.h +++ b/lisa/_assets/kmodules/lisa/utils.h @@ -10,10 +10,12 @@ # include "linux/args.h" #endif -#include "linux/kernel.h" -#include "linux/module.h" -#include "linux/version.h" -#include "linux/stringify.h" +#include +#include +#include +#include +#include + #define IGNORE_WARNING(warning, expr) ({ \ __diag_push(); \ @@ -47,4 +49,13 @@ # define LISA_MODULE_IMPORT_NS(ns) MODULE_IMPORT_NS(__stringify(ns)) #endif +// __assign_str() takes only one parameter since commit 2c92ca849fcc as +// __string() contains the source already. This allows ftrace to reserve the +// appropriate buffer size before-hand. +#if LINUX_VERSION_CODE < KERNEL_VERSION(6,10,0) +# define __lisa_assign_str(field, src) __assign_str(field, src) +#else +# define __lisa_assign_str(field, src) __assign_str(field) +#endif + #endif /* _UTILS_H */ diff --git a/lisa/_kmod.py b/lisa/_kmod.py index d20da9909ecb2c5f5ec327bfeb80e350cbfa420e..b8b2b1a39101438439122116a009b5314798c65c 100644 --- a/lisa/_kmod.py +++ b/lisa/_kmod.py @@ -3307,12 +3307,20 @@ class FtraceDynamicKmod(DynamicKmod): """ decomment = re.compile(rb'^.*//.*?$|/\*.*?\*/', flags=re.MULTILINE) - # We match trace_lisa__XXX tokens, as these are the events that are - # actually used inside the module. - find_event = re.compile(rb'\btrace_(lisa__.*?)\b') + find_event = [ + # We match trace_lisa__XXX tokens, as these are the events that + # are actually used inside the module C code. + re.compile(rb'\btrace_(lisa__.*?)\b'), + # Match the events defined in Rust code + re.compile(rb'\bnew_event\s*!\s*\(\s*(.*?)\s*,', flags=re.DOTALL), + re.compile(rb'\bnew_event\s*!\s*{\s*(.*?)\s*,', flags=re.DOTALL), + ] def find_events(code): code = decomment.sub(b'', code) - return map(bytes.decode, find_event.findall(code)) + return itertools.chain.from_iterable( + map(bytes.decode, regex.findall(code)) + for regex in find_event + ) return sorted({ possible_event