diff --git a/BUILTINS.md b/BUILTINS.md index ca9d293..220b1a8 100644 --- a/BUILTINS.md +++ b/BUILTINS.md @@ -85,7 +85,7 @@ fn main() -> i32 { #### `attach(handle, target, flags)` / `attach(handle, opts, flags)` **Signature:** `attach(handle: ProgramHandle, target: str(128), flags: u32) -> u32` -**Signature:** `attach(handle: ProgramHandle, opts: perf_options, flags: u32) -> PerfAttachment` +**Signature:** `attach(handle: ProgramHandle, opts: perf_options, flags: u32) -> perf_attachment` **Variadic:** No **Context:** Userspace only @@ -98,12 +98,12 @@ fn main() -> i32 { - `flags`: Attachment flags (context-dependent) - Perf event form: - `handle`: Program handle returned from `load()` - - `opts`: `perf_options` value — only `perf_type` and `perf_config` are required; all other fields have defaults, including no group (`group` invalid and `group_fd=-1`) + - `opts`: `perf_options` value — only `event` is required; all other fields have defaults, including no group (`group` defaults to an invalid attachment) - `flags`: Must be `0` for perf attaches; nonzero values are rejected **Return Value:** - Standard form returns `0` on success and an error code on failure -- Perf event form returns a `PerfAttachment` value with the open counter/link identity and an internal stale-handle token +- Perf event form returns a `perf_attachment` value with the open counter/link identity and an internal stale-handle token **Examples:** ```kernelscript @@ -113,26 +113,25 @@ if (result != 0) { print("Failed to attach program") } -// Minimal perf attach — all non-perf_type/perf_config fields use defaults: +// Minimal perf attach — all non-event fields use defaults: // pid=-1 (all procs), cpu=0, period=1_000_000, wakeup=1; perf attach flags must be 0 var perf_prog = load(on_branch_miss) -var perf_att = attach(perf_prog, perf_options { perf_type: perf_type_hardware, perf_config: branch_misses }, 0) +var perf_att = attach(perf_prog, perf_options { event: branch_misses }, 0) var count = read(perf_att).scaled detach(perf_att) detach(perf_prog) // Grouped perf events: branch joins cache's leader group. Adding a member restarts the group. -var cache = attach(perf_prog, perf_options { perf_type: perf_type_hardware, perf_config: cache_misses }, 0) +var cache = attach(perf_prog, perf_options { event: cache_misses }, 0) var branch = attach(perf_prog, perf_options { - perf_type: perf_type_hardware, - perf_config: branch_misses, + event: branch_misses, group: cache, }, 0) detach(branch) detach(cache) ``` -Grouped events are scheduled as one atomic PMU unit. Separate events and separate groups may be multiplexed, but members inside one group cannot be independently multiplexed. Static groups that exceed the target PMU counter limit are rejected at compile time; override the detected/default limit with `KERNELSCRIPT_PERF_GROUP_MAX_EVENTS` when compiling for a different target. The effective limit is capped at 16 to match `PerfRead`. +Grouped events are scheduled as one atomic PMU unit. Separate events and separate groups may be multiplexed, but members inside one group cannot be independently multiplexed. A group that needs more hardware PMU counters than the running host provides is rejected by the kernel at `perf_event_open(2)`, and `attach()` reports the error at runtime. `read(att)` exposes up to 16 group entries (`perf_read`). **Context-specific implementations:** - **eBPF:** Not available @@ -143,14 +142,14 @@ Grouped events are scheduled as one atomic PMU unit. Separate events and separat #### `detach(handle)` **Signature:** `detach(handle: ProgramHandle) -> void` -**Signature:** `detach(handle: PerfAttachment) -> void` +**Signature:** `detach(handle: perf_attachment) -> void` **Variadic:** No **Context:** Userspace only **Description:** Detach a loaded eBPF program from its current attachment point, or tear down one perf attachment. **Parameters:** -- `handle`: Program handle returned from `load()`, or a `PerfAttachment` returned from perf `attach()` +- `handle`: Program handle returned from `load()`, or a `perf_attachment` returned from perf `attach()` **Return Value:** - No return value (void) @@ -171,7 +170,7 @@ detach(prog) // Clean up --- #### `read(handle)` -**Signature:** `read(handle: PerfAttachment) -> PerfRead` +**Signature:** `read(handle: perf_attachment) -> perf_read` **Variadic:** No **Context:** Userspace only diff --git a/README.md b/README.md index 9b6af6f..0c00456 100644 --- a/README.md +++ b/README.md @@ -294,7 +294,7 @@ fn main() -> i32 { ### Hardware Performance Counter Programs -Use `@perf_event` to attach eBPF programs to hardware or software performance counters. `perf_options` keeps the kernel's tagged `perf_type + perf_config` model, so adding new perf event families does not require flattening everything into one enum. Only `perf_type` and `perf_config` are required; all other fields have sensible defaults. Perf attaches return a first-class attachment value, so if you need the current count in userspace, call `read(att).scaled`: +Use `@perf_event` to attach eBPF programs to hardware or software performance counters. Each event is named by a single `event` value that carries both the kernel `perf_event_attr.type` tag and its config, so the type can never be mismatched with the config. Only `event` is required; all other fields have sensible defaults. Perf attaches return a first-class attachment value, so if you need the current count in userspace, call `read(att).scaled`: ```kernelscript // eBPF program fires on every hardware branch-miss sample @@ -308,7 +308,7 @@ fn main() -> i32 { // Minimal form — defaults: pid=-1 (all procs), cpu=0, no group, // period=1_000_000, wakeup=1; perf attach flags must be 0 - var att = attach(prog, perf_options { perf_type: perf_type_hardware, perf_config: branch_misses }, 0) + var att = attach(prog, perf_options { event: branch_misses }, 0) var count = read(att).scaled print("branch misses: %lld", count) @@ -318,48 +318,35 @@ fn main() -> i32 { } ``` -Perf events can share a kernel scheduling group by passing the leader attachment directly with `group`. -The lower-level `group_fd: cache.perf_fd` form is still supported for compatibility: +Perf events can share a kernel scheduling group by passing the leader attachment with `group`: ```kernelscript -var cache = attach(prog, perf_options { perf_type: perf_type_hardware, perf_config: cache_misses }, 0) +var cache = attach(prog, perf_options { event: cache_misses }, 0) var branch = attach(prog, perf_options { - perf_type: perf_type_hardware, - perf_config: branch_misses, + event: branch_misses, group: cache, }, 0) ``` -Adding a member restarts the whole group from zero. Detaching a leader cascades to any live members. A group competes for PMU counters as one atomic unit: different groups can be multiplexed over time, but members inside one group are not independently multiplexed. For statically visible groups, the compiler rejects groups that need more PMU counter slots than the target limit. The limit is read from known sysfs PMU caps when available, defaults to 4, can be overridden with `KERNELSCRIPT_PERF_GROUP_MAX_EVENTS`, and is capped at 16 to match `PerfRead`. +Adding a member restarts the whole group from zero. Detaching a leader cascades to any live members. A group competes for PMU counters as one atomic unit: different groups can be multiplexed over time, but members inside one group are not independently multiplexed. A group that needs more hardware PMU counters than the running host provides is rejected by the kernel at `perf_event_open(2)`, and `attach()` surfaces the error at runtime — on the actual deployment host, where the real counter count is known. `read(att)` exposes up to 16 group entries (`perf_read`). -`read(att)` returns a `PerfRead` snapshot with raw, multiplex-scaled, timing, and group fields. Use `read(att).scaled` for that attachment's counter value, `read(att).raw` for its unscaled value, and `read(att).values` / `read(att).ids` for a same-time group snapshot. +`read(att)` returns a `perf_read` snapshot with raw, multiplex-scaled, timing, and group fields. Use `read(att).scaled` for that attachment's counter value, `read(att).raw` for its unscaled value, and `read(att).values` / `read(att).ids` for a same-time group snapshot. -**Available `perf_type` values:** +**Available `event` constants:** -| Enum value | Hardware/software event | -|---|---| -| `perf_type_hardware` | `PERF_TYPE_HARDWARE` | -| `perf_type_software` | `PERF_TYPE_SOFTWARE` | -| `perf_type_tracepoint` | `PERF_TYPE_TRACEPOINT` | -| `perf_type_hw_cache` | `PERF_TYPE_HW_CACHE` | -| `perf_type_raw` | `PERF_TYPE_RAW` | -| `perf_type_breakpoint` | `PERF_TYPE_BREAKPOINT` | +Each constant packs its `perf_event_attr.type` tag in the high 32 bits and its config in the low 32 bits, so naming the event fixes both. -**Common `perf_config` constants:** - -| Constant | Intended `perf_type` | Linux config | +| Constant | Kernel type | Linux config | |---|---|---| -| `cpu_cycles` | `perf_type_hardware` | `PERF_COUNT_HW_CPU_CYCLES` | -| `instructions` | `perf_type_hardware` | `PERF_COUNT_HW_INSTRUCTIONS` | -| `cache_references` | `perf_type_hardware` | `PERF_COUNT_HW_CACHE_REFERENCES` | -| `cache_misses` | `perf_type_hardware` | `PERF_COUNT_HW_CACHE_MISSES` | -| `branch_instructions` | `perf_type_hardware` | `PERF_COUNT_HW_BRANCH_INSTRUCTIONS` | -| `branch_misses` | `perf_type_hardware` | `PERF_COUNT_HW_BRANCH_MISSES` | -| `page_faults` | `perf_type_software` | `PERF_COUNT_SW_PAGE_FAULTS` | -| `context_switches` | `perf_type_software` | `PERF_COUNT_SW_CONTEXT_SWITCHES` | -| `cpu_migrations` | `perf_type_software` | `PERF_COUNT_SW_CPU_MIGRATIONS` | - -For newer families such as `perf_type_hw_cache`, pass the kernel-compatible encoded `perf_config` value directly. +| `cpu_cycles` | `PERF_TYPE_HARDWARE` | `PERF_COUNT_HW_CPU_CYCLES` | +| `instructions` | `PERF_TYPE_HARDWARE` | `PERF_COUNT_HW_INSTRUCTIONS` | +| `cache_references` | `PERF_TYPE_HARDWARE` | `PERF_COUNT_HW_CACHE_REFERENCES` | +| `cache_misses` | `PERF_TYPE_HARDWARE` | `PERF_COUNT_HW_CACHE_MISSES` | +| `branch_instructions` | `PERF_TYPE_HARDWARE` | `PERF_COUNT_HW_BRANCH_INSTRUCTIONS` | +| `branch_misses` | `PERF_TYPE_HARDWARE` | `PERF_COUNT_HW_BRANCH_MISSES` | +| `page_faults` | `PERF_TYPE_SOFTWARE` | `PERF_COUNT_SW_PAGE_FAULTS` | +| `context_switches` | `PERF_TYPE_SOFTWARE` | `PERF_COUNT_SW_CONTEXT_SWITCHES` | +| `cpu_migrations` | `PERF_TYPE_SOFTWARE` | `PERF_COUNT_SW_CPU_MIGRATIONS` | 📖 **For detailed language specification, syntax reference, and advanced features, please read [`SPEC.md`](SPEC.md).** diff --git a/SPEC.md b/SPEC.md index 2aa7e5f..6a145c8 100644 --- a/SPEC.md +++ b/SPEC.md @@ -460,25 +460,22 @@ The context type is always `*bpf_perf_event_data` (from `vmlinux.h`). fn main() -> i32 { var prog = load(my_handler) - // Only perf_type + perf_config are required; all other fields use language-level defaults: + // Only event is required; all other fields use language-level defaults: // pid=-1, cpu=0, no group, period=1_000_000, wakeup=1, inherit/exclude_*=false - var misses = attach(prog, perf_options { perf_type: perf_type_hardware, perf_config: branch_misses }, 0) + var misses = attach(prog, perf_options { event: branch_misses }, 0) // Override specific fields as needed: var cache = attach(prog, perf_options { - perf_type: perf_type_hardware, - perf_config: cache_misses, + event: cache_misses, cpu: 2, period: 500000, exclude_kernel: true, }, 0) // Put branch misses in cache's perf event group. Adding a member restarts - // the whole group from zero. The lower-level group_fd: cache.perf_fd form - // is still accepted. + // the whole group from zero. var branch = attach(prog, perf_options { - perf_type: perf_type_hardware, - perf_config: branch_misses, + event: branch_misses, group: cache, }, 0) @@ -497,12 +494,10 @@ fn main() -> i32 { | Field | Type | Default | Description | |---|---|---|---| -| `perf_type` | `perf_type` | *(required)* | `perf_event_attr.type` tag | -| `perf_config` | `u64` | *(required)* | `perf_event_attr.config` value for that type | +| `event` | `perf_event` | *(required)* | Named event; packs the `perf_event_attr.type` tag and config into one value | | `pid` | `i32` | `-1` | -1 = all processes; ≥0 = specific PID | | `cpu` | `i32` | `0` | ≥0 = specific CPU; -1 = any CPU (pid must be ≥0) | -| `group_fd` | `i32` | `-1` | -1 = standalone event; ≥0 = perf group leader fd | -| `group` | `PerfAttachment` | invalid attachment | Preferred high-level group leader attachment | +| `group` | `perf_attachment` | invalid attachment | Group leader attachment; omit for a standalone event | | `period` | `u64` | `1000000` | Sample after this many events | | `wakeup` | `u32` | `1` | Wake userspace after N samples | | `inherit` | `bool` | `false` | Inherit to forked children | @@ -518,40 +513,29 @@ fn main() -> i32 { | -1 | ≥ 0 | All processes on specific CPU (system-wide) | | -1 | -1 | **Invalid** — rejected with error | -**`perf_type` enum:** +**`perf_event` enum:** -| Value | Linux constant | -|---|---| -| `perf_type_hardware` | `PERF_TYPE_HARDWARE` | -| `perf_type_software` | `PERF_TYPE_SOFTWARE` | -| `perf_type_tracepoint` | `PERF_TYPE_TRACEPOINT` | -| `perf_type_hw_cache` | `PERF_TYPE_HW_CACHE` | -| `perf_type_raw` | `PERF_TYPE_RAW` | -| `perf_type_breakpoint` | `PERF_TYPE_BREAKPOINT` | +Each constant packs its `perf_event_attr.type` tag in the high 32 bits and its config in the low 32 bits, so naming the event fixes both and the type can never be mismatched with the config. -**Common `perf_config` constants:** - -| Value | Intended `perf_type` | Linux constant | +| Value | Kernel type | Linux config constant | |---|---|---| -| `cpu_cycles` | `perf_type_hardware` | `PERF_COUNT_HW_CPU_CYCLES` | -| `instructions` | `perf_type_hardware` | `PERF_COUNT_HW_INSTRUCTIONS` | -| `cache_references` | `perf_type_hardware` | `PERF_COUNT_HW_CACHE_REFERENCES` | -| `cache_misses` | `perf_type_hardware` | `PERF_COUNT_HW_CACHE_MISSES` | -| `branch_instructions` | `perf_type_hardware` | `PERF_COUNT_HW_BRANCH_INSTRUCTIONS` | -| `branch_misses` | `perf_type_hardware` | `PERF_COUNT_HW_BRANCH_MISSES` | -| `page_faults` | `perf_type_software` | `PERF_COUNT_SW_PAGE_FAULTS` | -| `context_switches` | `perf_type_software` | `PERF_COUNT_SW_CONTEXT_SWITCHES` | -| `cpu_migrations` | `perf_type_software` | `PERF_COUNT_SW_CPU_MIGRATIONS` | - -For event families with a richer config space, such as `perf_type_hw_cache`, provide the encoded kernel `perf_config` value directly instead of relying on a flattened enum. +| `cpu_cycles` | `PERF_TYPE_HARDWARE` | `PERF_COUNT_HW_CPU_CYCLES` | +| `instructions` | `PERF_TYPE_HARDWARE` | `PERF_COUNT_HW_INSTRUCTIONS` | +| `cache_references` | `PERF_TYPE_HARDWARE` | `PERF_COUNT_HW_CACHE_REFERENCES` | +| `cache_misses` | `PERF_TYPE_HARDWARE` | `PERF_COUNT_HW_CACHE_MISSES` | +| `branch_instructions` | `PERF_TYPE_HARDWARE` | `PERF_COUNT_HW_BRANCH_INSTRUCTIONS` | +| `branch_misses` | `PERF_TYPE_HARDWARE` | `PERF_COUNT_HW_BRANCH_MISSES` | +| `page_faults` | `PERF_TYPE_SOFTWARE` | `PERF_COUNT_SW_PAGE_FAULTS` | +| `context_switches` | `PERF_TYPE_SOFTWARE` | `PERF_COUNT_SW_CONTEXT_SWITCHES` | +| `cpu_migrations` | `PERF_TYPE_SOFTWARE` | `PERF_COUNT_SW_CPU_MIGRATIONS` | **Generated C helpers (emitted when `attach(prog, perf_options{...}, flags)` is used):** | Function | Signature | Description | |---|---|---| | `ks_open_perf_event` | `int (ks_perf_options)` | Calls `perf_event_open(2)`, returns fd | -| `ks_attach_perf_event` | `PerfAttachment (int prog_fd, ks_perf_options, int flags)` | Full open-reset-attach-enable lifecycle | -| `ks_perf_attachment_read` | `PerfRead (PerfAttachment)` | Direct fd snapshot through the attachment value with stale-handle detection | +| `ks_attach_perf_event` | `perf_attachment (int prog_fd, ks_perf_options, int flags)` | Full open-reset-attach-enable lifecycle | +| `ks_perf_attachment_read` | `perf_read (perf_attachment)` | Direct fd snapshot through the attachment value with stale-handle detection | **Attach sequence for standalone events (compiler-generated, inside `ks_attach_perf_event`):** 1. `ks_attr.attr.disabled = 1` — open counter without starting it @@ -561,19 +545,16 @@ For event families with a richer config space, such as `perf_type_hw_cache`, pro 5. `ioctl(perf_fd, PERF_EVENT_IOC_ENABLE, 0)` — **start counting** **Perf event groups:** -- `group: leader_attachment` is the preferred way to join a perf group. -- `group_fd >= 0` opens the new event as a member of that leader fd. +- `group: leader_attachment` joins the new event to that leader's perf group. - Group members are opened disabled, linked to the BPF program, then the leader is disabled, reset, and enabled with `PERF_IOC_FLAG_GROUP`. - Adding a member to an already running group restarts the whole group from zero. -- A group is scheduled as an atomic PMU unit. Separate events and separate groups may be multiplexed; members inside one group are not independently multiplexed. If a statically visible group needs more PMU counter slots than the target limit, compilation fails. -- The compile-time group limit uses known sysfs PMU caps when available, falls back to `4`, can be overridden with `KERNELSCRIPT_PERF_GROUP_MAX_EVENTS`, and is capped at the 16 entries exposed by `PerfRead`. -- `perf_type_software` and `perf_type_tracepoint` do not consume PMU counter slots for this check; static hardware/raw/cache/breakpoint events consume one slot, and dynamic `perf_type` values are conservatively counted as one slot. +- A group is scheduled as an atomic PMU unit. Separate events and separate groups may be multiplexed; members inside one group are not independently multiplexed. A group that needs more hardware PMU counters than the running host provides is rejected by the kernel at `perf_event_open(2)` and reported by `attach()` at runtime — group sizing is enforced on the actual host, not guessed at compile time. - Detaching a member is allowed. Detaching a leader cascades to any live members. - Generated perf events always enable `PERF_FORMAT_GROUP | PERF_FORMAT_ID`, and `read(att)` returns up to 16 same-time group values plus perf IDs and timing fields. `raw` and `scaled` select the entry matching the attachment being read. **Counter reads:** - Generated perf events request `PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING | PERF_FORMAT_ID | PERF_FORMAT_GROUP`. -- `read(att)` returns a `PerfRead` snapshot with `raw`, `scaled`, `time_enabled`, `time_running`, `count`, `values`, and `ids`. +- `read(att)` returns a `perf_read` snapshot with `raw`, `scaled`, `time_enabled`, `time_running`, `count`, `values`, and `ids`. - `read(att).scaled` equals this attachment's raw value when `time_enabled == time_running`. - If multiplexing occurred, `read(att).scaled` is `value * time_enabled / time_running` using a 128-bit intermediate. - If `time_running == 0`, `read(att)` reports an error and returns `scaled == -1`. @@ -588,11 +569,11 @@ For event families with a richer config space, such as `perf_type_hw_cache`, pro **Compiler implementation:** - Detects `attach(prog, perf_options_value, flags)` (three-argument form with `perf_options` second arg) and routes to `ks_attach_perf_event` - Requires perf attach `flags` to be `0`; nonzero values are rejected instead of being silently ignored -- Returns a first-class `PerfAttachment` value for perf attaches so one program can hold multiple live counters -- `PerfAttachment` carries `perf_fd` plus an internal generation token; `read(attachment)` avoids global attachment-list scans and rejects copied handles after detach +- Returns a first-class `perf_attachment` value for perf attaches so one program can hold multiple live counters +- `perf_attachment` carries `perf_fd` plus an internal generation token; `read(attachment)` avoids global attachment-list scans and rejects copied handles after detach - Exposes omitted `perf_options` fields as language-level defaults (partial struct literal) - Validates `pid ≥ -1`, `cpu ≥ -1`, `group_fd ≥ -1`, and rejects `pid == -1 && cpu == -1` at runtime -- Treats `group` as valid only when it carries a live `PerfAttachment` generation token; otherwise `group_fd` controls grouping +- Treats `group` as valid only when it carries a live `perf_attachment` generation token; otherwise the event is opened standalone - Emits `PERF_FLAG_FD_CLOEXEC` for safe fd inheritance - BPF program section is `SEC("perf_event")` diff --git a/examples/perf_cache_miss.ks b/examples/perf_cache_miss.ks index 24c3a14..248c6a5 100644 --- a/examples/perf_cache_miss.ks +++ b/examples/perf_cache_miss.ks @@ -11,12 +11,12 @@ fn on_cache_miss(ctx: *bpf_perf_event_data) -> i32 { fn main() -> i32 { var prog = load(on_cache_miss) - // Only perf_type + perf_config are required; pid, cpu, group/group_fd, period, wakeup and flag fields + // Only event is required; pid, cpu, group, period, wakeup and flag fields // default to: pid=-1 (all procs), cpu=0, period=1_000_000, wakeup=1, // no group, inherit/exclude_kernel/exclude_user=false. - var cache = attach(prog, perf_options { perf_type: perf_type_hardware, perf_config: cache_misses, period: 10000000, inherit: true }, 0) + var cache = attach(prog, perf_options { event: cache_misses, period: 10000000, inherit: true }, 0) // branch joins cache's perf event group. Adding a member restarts the whole group from zero. - var branch = attach(prog, perf_options { perf_type: perf_type_hardware, perf_config: branch_misses, period: 10000000, inherit: true, group: cache }, 0) + var branch = attach(prog, perf_options { event: branch_misses, period: 10000000, inherit: true, group: cache }, 0) print("Cache-miss and branch-miss perf_event demo attached") var cache_count = read(cache).scaled print("Cache-miss count: %lld", cache_count) diff --git a/examples/perf_page_fault.ks b/examples/perf_page_fault.ks index ece35de..73dad6c 100644 --- a/examples/perf_page_fault.ks +++ b/examples/perf_page_fault.ks @@ -14,9 +14,9 @@ fn main() -> i32 { // pid: 0 = current process, cpu: -1 = any CPU (standard per-process monitoring). // page_faults (PERF_COUNT_SW_PAGE_FAULTS) is the most reliable software event: // every heap/stack allocation triggers minor page faults, no scheduler dependency. - var page = attach(prog, perf_options { perf_type: perf_type_software, perf_config: page_faults, pid: 0, cpu: -1, period: 1 }, 0) + var page = attach(prog, perf_options { event: page_faults, pid: 0, cpu: -1, period: 1 }, 0) // branch is a standalone hardware event; page_faults remains a separate software event. - var branch = attach(prog, perf_options { perf_type: perf_type_hardware, perf_config: branch_misses, period: 10000000, inherit: true}, 0) + var branch = attach(prog, perf_options { event: branch_misses, period: 10000000, inherit: true}, 0) print("perf_event demo attached") diff --git a/src/btf_parser.ml b/src/btf_parser.ml index f88c815..b4ea9d5 100644 --- a/src/btf_parser.ml +++ b/src/btf_parser.ml @@ -521,8 +521,8 @@ let generate_kernelscript_source ?extra_param ?include_kfuncs template project_n fn main() -> i32 { var prog = load(%s) - // perf_type + perf_config are required; all other fields default to sensible values. - var att = attach(prog, perf_options { perf_type: perf_type_hardware, perf_config: branch_misses }, 0) + // event is required; all other fields default to sensible values. + var att = attach(prog, perf_options { event: branch_misses }, 0) detach(att) detach(prog) diff --git a/src/stdlib.ml b/src/stdlib.ml index d05f37f..6d9b894 100644 --- a/src/stdlib.ml +++ b/src/stdlib.ml @@ -110,17 +110,17 @@ let validate_register_function arg_types ast_context _pos = (false, Some "register() requires an impl block argument") (** Read dispatch metadata keyed by argument type. - This keeps read() trait-shaped even while only PerfAttachment is supported. *) + This keeps read() trait-shaped even while only perf_attachment is supported. *) type read_dispatch = { read_return_type: bpf_type; read_userspace_impl: string; } let read_dispatch_for_type = function - | Struct "PerfAttachment" - | UserType "PerfAttachment" -> + | Struct "perf_attachment" + | UserType "perf_attachment" -> Some { - read_return_type = Struct "PerfRead"; + read_return_type = Struct "perf_read"; read_userspace_impl = "ks_perf_attachment_read"; } | _ -> None @@ -143,7 +143,7 @@ let validate_read_function arg_types _ast_context _pos = | [arg_type] -> (match read_dispatch_for_type arg_type with | Some _ -> (true, None) - | None -> (false, Some "read() currently requires a PerfAttachment")) + | None -> (false, Some "read() currently requires a perf_attachment")) | _ -> (false, Some "read() currently requires exactly one argument") @@ -151,11 +151,11 @@ let validate_read_function arg_types _ast_context _pos = let validate_detach_function arg_types _ast_context _pos = match arg_types with | [ProgramHandle] - | [Struct "PerfAttachment"] - | [UserType "PerfAttachment"] -> + | [Struct "perf_attachment"] + | [UserType "perf_attachment"] -> (true, None) | _ -> - (false, Some "detach() requires a ProgramHandle or PerfAttachment") + (false, Some "detach() requires a ProgramHandle or perf_attachment") (** Standard library built-in functions *) let builtin_functions = [ @@ -261,7 +261,7 @@ let builtin_functions = [ { name = "read"; param_types = []; (* Custom validation handles attachment-aware overloads *) - return_type = Struct "PerfRead"; + return_type = Struct "perf_read"; description = "Read raw/scaled/timing values and group snapshot arrays for a perf attachment"; is_variadic = false; ebpf_impl = ""; (* Not available in eBPF context *) @@ -311,6 +311,16 @@ let builtin_pos = { line = 0; column = 0; filename = "" } let perf_read_max_values = 16 +(* A perf_event value packs the perf_event_attr.type tag in the high 32 bits and the + config in the low 32 bits, so naming an event fixes both. perf_event_pack is the + single source of truth for that layout; the C side mirrors it with the + KS_PERF_EVENT macros in userspace_codegen.ml. *) +let perf_type_hardware = 0 +let perf_type_software = 1 + +let perf_event_pack ptype config = + Int64.logor (Int64.shift_left (Int64.of_int ptype) 32) (Int64.of_int config) + let builtin_types = [ (* Standard C types as type aliases *) TypeDef (TypeAlias ("size_t", U64, builtin_pos)); (* size_t maps to 64-bit unsigned integer *) @@ -335,42 +345,28 @@ let builtin_types = [ ("TC_ACT_TRAP", Some (Ast.Signed64 8L)); ], builtin_pos)); - (* perf_type mirrors perf_event_attr.type so config stays a tagged 2D space. *) - TypeDef (EnumDef ("perf_type", [ - ("perf_type_hardware", Some (Ast.Signed64 0L)); - ("perf_type_software", Some (Ast.Signed64 1L)); - ("perf_type_tracepoint", Some (Ast.Signed64 2L)); - ("perf_type_hw_cache", Some (Ast.Signed64 3L)); - ("perf_type_raw", Some (Ast.Signed64 4L)); - ("perf_type_breakpoint", Some (Ast.Signed64 5L)); - ], builtin_pos)); - - (* Common config values for PERF_TYPE_HARDWARE. *) - TypeDef (EnumDef ("perf_hw_config", [ - ("cpu_cycles", Some (Ast.Signed64 0L)); - ("instructions", Some (Ast.Signed64 1L)); - ("cache_references", Some (Ast.Signed64 2L)); - ("cache_misses", Some (Ast.Signed64 3L)); - ("branch_instructions", Some (Ast.Signed64 4L)); - ("branch_misses", Some (Ast.Signed64 5L)); - ], builtin_pos)); - - (* Common config values for PERF_TYPE_SOFTWARE. *) - TypeDef (EnumDef ("perf_sw_config", [ - ("page_faults", Some (Ast.Signed64 2L)); - ("context_switches", Some (Ast.Signed64 3L)); - ("cpu_migrations", Some (Ast.Signed64 4L)); + (* perf_event: a named hardware or software event. Each value is built with + perf_event_pack so the perf_event_attr.type tag and config travel together and + can never be mismatched. The C #defines in userspace_codegen.ml mirror this. *) + TypeDef (EnumDef ("perf_event", [ + ("cpu_cycles", Some (Ast.Signed64 (perf_event_pack perf_type_hardware 0))); + ("instructions", Some (Ast.Signed64 (perf_event_pack perf_type_hardware 1))); + ("cache_references", Some (Ast.Signed64 (perf_event_pack perf_type_hardware 2))); + ("cache_misses", Some (Ast.Signed64 (perf_event_pack perf_type_hardware 3))); + ("branch_instructions", Some (Ast.Signed64 (perf_event_pack perf_type_hardware 4))); + ("branch_misses", Some (Ast.Signed64 (perf_event_pack perf_type_hardware 5))); + ("page_faults", Some (Ast.Signed64 (perf_event_pack perf_type_software 2))); + ("context_switches", Some (Ast.Signed64 (perf_event_pack perf_type_software 3))); + ("cpu_migrations", Some (Ast.Signed64 (perf_event_pack perf_type_software 4))); ], builtin_pos)); (* perf_options: configuration bag for @perf_event programs. - Only 'perf_type' and 'perf_config' are required; all other fields have language-level defaults. *) + Only 'event' is required; all other fields have language-level defaults. *) TypeDef (StructDef ("perf_options", [ - ("perf_type", Enum "perf_type"); - ("perf_config", U64); + ("event", Enum "perf_event"); ("pid", I32); ("cpu", I32); - ("group_fd", I32); - ("group", Struct "PerfAttachment"); + ("group", Struct "perf_attachment"); ("period", U64); ("wakeup", U32); ("inherit", Bool); @@ -378,15 +374,15 @@ let builtin_types = [ ("exclude_user", Bool); ], builtin_pos)); - (* PerfAttachment: first-class userspace handle returned by perf_event attach(). *) - TypeDef (StructDef ("PerfAttachment", [ + (* perf_attachment: first-class userspace handle returned by perf_event attach(). *) + TypeDef (StructDef ("perf_attachment", [ ("perf_fd", I32); ("link_id", I32); ("prog_fd", I32); ("generation", U64); ], builtin_pos)); - TypeDef (StructDef ("PerfRead", [ + TypeDef (StructDef ("perf_read", [ ("raw", I64); ("scaled", I64); ("time_enabled", U64); @@ -399,15 +395,14 @@ let builtin_types = [ (** Default field values for structs that support partial initialisation. Returns [(field_name, default_literal)] for optional fields only. - Required fields (e.g. perf_type/perf_config in perf_options) are absent from the list, + Required fields (e.g. event in perf_options) are absent from the list, so the type checker will still error if they are omitted. *) let get_struct_field_defaults = function | "perf_options" -> Some [ ("pid", Literal (IntLit (Signed64 (-1L), None))); ("cpu", Literal (IntLit (Signed64 0L, None))); - ("group_fd", Literal (IntLit (Signed64 (-1L), None))); - ("group", StructLiteral ("PerfAttachment", [ + ("group", StructLiteral ("perf_attachment", [ ("perf_fd", make_expr (Literal (IntLit (Signed64 (-1L), None))) builtin_pos); ("link_id", make_expr (Literal (IntLit (Signed64 (-1L), None))) builtin_pos); ("prog_fd", make_expr (Literal (IntLit (Signed64 (-1L), None))) builtin_pos); diff --git a/src/type_checker.ml b/src/type_checker.ml index 5cb2815..916fbe1 100644 --- a/src/type_checker.ml +++ b/src/type_checker.ml @@ -208,229 +208,9 @@ let loop_depth = ref 0 (** Helper to create type error *) let type_error msg pos = raise (Type_error (msg, pos)) -let getenv_opt name = - try Some (Sys.getenv name) with Not_found -> None - let hashtbl_find_opt tbl key = try Some (Hashtbl.find tbl key) with Not_found -> None -let parse_positive_int text = - try - let value = int_of_string (String.trim text) in - if value > 0 then Some value else None - with _ -> None - -let read_positive_int_file path = - try - let ic = open_in path in - Fun.protect - ~finally:(fun () -> close_in_noerr ic) - (fun () -> parse_positive_int (input_line ic)) - with _ -> None - -let detected_perf_group_max_events () = - let detected = match getenv_opt "KERNELSCRIPT_PERF_GROUP_MAX_EVENTS" with - | Some value -> - (match parse_positive_int value with - | Some parsed -> parsed - | None -> 4) - | None -> - (* Some PMU drivers expose a counter count in sysfs; many do not. - Use a conservative 4-counter fallback, matching common generic PMUs. *) - let candidate_files = [ - "/sys/bus/event_source/devices/cpu/caps/num_counters"; - "/sys/bus/event_source/devices/cpu/caps/num_events"; - ] in - (match List.find_map read_positive_int_file candidate_files with - | Some detected -> detected - | None -> 4) - in - min detected Stdlib.perf_read_max_values - -type perf_attach_group_ref = { - perf_attach_name: string; - perf_attach_leader: string option; - perf_attach_pmu_slots: int; - perf_attach_pos: position; -} - -let is_attach_to_perf_options expr = - match expr.expr_desc with - | Call ({ expr_desc = Identifier "attach"; _ }, [_prog; opts; _flags]) -> - (match opts.expr_desc with - | StructLiteral ("perf_options", _) -> true - | _ -> false) - | _ -> false - -let perf_options_static_group_leader expr = - match expr.expr_desc with - | StructLiteral ("perf_options", fields) -> - let group_leader = - match List.assoc_opt "group" fields with - | Some { expr_desc = Identifier leader; _ } -> Some leader - | _ -> None - in - (match group_leader with - | Some _ -> group_leader - | None -> - (match List.assoc_opt "group_fd" fields with - | Some { expr_desc = FieldAccess ({ expr_desc = Identifier leader; _ }, "perf_fd"); _ } -> - Some leader - | _ -> None)) - | _ -> None - -let perf_type_consumes_pmu_slot expr = - match expr.expr_desc with - | Identifier "perf_type_software" - | Identifier "perf_type_tracepoint" -> 0 - | Literal (IntLit (Signed64 value, _)) when value = 1L || value = 2L -> 0 - | Literal (IntLit (Unsigned64 value, _)) when value = 1L || value = 2L -> 0 - | _ -> 1 - -let perf_options_pmu_slots expr = - match expr.expr_desc with - | StructLiteral ("perf_options", fields) -> - (match List.assoc_opt "perf_type" fields with - | Some perf_type_expr -> perf_type_consumes_pmu_slot perf_type_expr - | None -> 1) - | _ -> 1 - -let perf_attach_decl_from_expr name expr pos = - match expr.expr_desc with - | Call ({ expr_desc = Identifier "attach"; _ }, [_prog; opts; _flags]) - when is_attach_to_perf_options expr -> - Some { - perf_attach_name = name; - perf_attach_leader = perf_options_static_group_leader opts; - perf_attach_pmu_slots = perf_options_pmu_slots opts; - perf_attach_pos = pos; - } - | _ -> None - -let validate_static_perf_event_groups_in_function func = - let collect_expr acc _expr = acc in - let rec collect_statement acc stmt = - match stmt.stmt_desc with - | Declaration (name, _, Some expr) - | ConstDeclaration (name, _, expr) -> - let acc = - match perf_attach_decl_from_expr name expr stmt.stmt_pos with - | Some attach -> attach :: acc - | None -> acc - in - collect_expr acc expr - | Declaration (_, _, None) -> acc - | ExprStmt expr - | Assignment (_, expr) - | CompoundAssignment (_, _, expr) - | Throw expr - | Defer expr - | FieldAssignment (_, _, expr) - | ArrowAssignment (_, _, expr) - | IndexAssignment (_, _, expr) -> - collect_expr acc expr - | CompoundIndexAssignment (_, _, _, expr) - | CompoundFieldIndexAssignment (_, _, _, _, expr) -> - collect_expr acc expr - | Return (Some expr) -> collect_expr acc expr - | Return None - | Break - | Continue - | Delete _ -> acc - | If (_cond, then_body, else_body) -> - let acc = collect_statements acc then_body in - (match else_body with - | Some body -> collect_statements acc body - | None -> acc) - | IfLet (_, expr, then_body, else_body) -> - let acc = collect_expr acc expr in - let acc = collect_statements acc then_body in - (match else_body with - | Some body -> collect_statements acc body - | None -> acc) - | For (_, start_expr, end_expr, body) -> - collect_statements (collect_expr (collect_expr acc start_expr) end_expr) body - | ForIter (_, _, expr, body) - | While (expr, body) -> - collect_statements (collect_expr acc expr) body - | Try (try_body, catch_clauses) -> - List.fold_left - (fun acc clause -> collect_statements acc clause.catch_body) - (collect_statements acc try_body) - catch_clauses - and collect_statements acc stmts = - List.fold_left collect_statement acc stmts - in - let attachments = collect_statements [] func.func_body |> List.rev in - let attachment_by_name = Hashtbl.create 16 in - let parent_by_name = Hashtbl.create 16 in - List.iter (fun attach -> - Hashtbl.replace attachment_by_name attach.perf_attach_name attach; - match attach.perf_attach_leader with - | Some leader -> Hashtbl.replace parent_by_name attach.perf_attach_name leader - | None -> () - ) attachments; - - let rec root_of seen name pos = - if List.mem name seen then - type_error ("perf event group contains a cycle at attachment '" ^ name ^ "'") pos - else - match hashtbl_find_opt parent_by_name name with - | None -> name - | Some parent -> - (match hashtbl_find_opt parent_by_name parent with - | Some _ -> - let root = root_of (name :: seen) parent pos in - type_error - ("perf event group member '" ^ name ^ "' uses '" ^ parent ^ - "' as its leader, but '" ^ parent ^ - "' is already a group member; use root leader '" ^ root ^ "'") - pos - | None -> parent) - in - - let max_group_events = detected_perf_group_max_events () in - let slot_counts = Hashtbl.create 8 in - let member_counts = Hashtbl.create 8 in - let positions = Hashtbl.create 8 in - List.iter (fun attach -> - let root = - match attach.perf_attach_leader with - | None -> attach.perf_attach_name - | Some _ -> root_of [] attach.perf_attach_name attach.perf_attach_pos - in - if Hashtbl.mem attachment_by_name root then ( - Hashtbl.replace slot_counts root (attach.perf_attach_pmu_slots + Option.value ~default:0 (hashtbl_find_opt slot_counts root)); - Hashtbl.replace member_counts root (1 + Option.value ~default:0 (hashtbl_find_opt member_counts root)); - if not (Hashtbl.mem positions root) then - Hashtbl.replace positions root attach.perf_attach_pos - ) - ) attachments; - Hashtbl.iter (fun root count -> - if count > max_group_events then - let pos = Option.value ~default:func.func_pos (hashtbl_find_opt positions root) in - type_error - (Printf.sprintf - "perf event group rooted at '%s' needs %d PMU counter slot(s), but target PMU group limit is %d; split the events into separate groups or reduce the group size" - root count max_group_events) - pos - ) slot_counts; - Hashtbl.iter (fun root count -> - if count > max_group_events then - let pos = Option.value ~default:func.func_pos (hashtbl_find_opt positions root) in - type_error - (Printf.sprintf - "perf event group rooted at '%s' has %d member(s), but target perf group limit is %d; split the events into separate groups or reduce the group size" - root count max_group_events) - pos - ) member_counts - -let validate_static_perf_event_groups ast = - List.iter (function - | GlobalFunction func -> validate_static_perf_event_groups_in_function func - | _ -> () - ) ast - (** Validate void function usage in expression context *) let validate_void_in_expression expr_type func_name context pos = match expr_type, context with @@ -697,7 +477,7 @@ let can_assign to_type from_type = let builtin_return_type_for_call name arg_types default_return_type = match name, arg_types with | "attach", [ProgramHandle; (Struct "perf_options" | UserType "perf_options"); _] -> - Struct "PerfAttachment" + Struct "perf_attachment" | "detach", _ -> Void | "read", [arg_type] -> @@ -3385,8 +3165,6 @@ let rec type_check_and_annotate_ast ?symbol_table:(provided_symbol_table=None) ? () ) ast; - validate_static_perf_event_groups ast; - (* Second pass: type check attributed functions and global functions with multi-program awareness *) let (typed_attributed_functions, typed_userspace_functions) = List.fold_left (fun (attr_acc, userspace_acc) decl -> match decl with diff --git a/src/userspace_codegen.ml b/src/userspace_codegen.ml index 55dac8b..2246a83 100644 --- a/src/userspace_codegen.ml +++ b/src/userspace_codegen.ml @@ -53,7 +53,7 @@ type read_codegen_dispatch = { } let read_codegen_dispatch_for_type = function - | IRStruct ("PerfAttachment", _) -> + | IRStruct ("perf_attachment", _) -> Some { read_codegen_userspace_impl = "ks_perf_attachment_read" } | _ -> None @@ -2128,7 +2128,7 @@ let rec generate_c_instruction_from_ir ctx instruction = (* Special handling for detach: accepts program handles and perf attachments *) ctx.function_usage.uses_detach <- true; (match args, c_args with - | [attachment], [attachment_arg] when (match attachment.val_type with IRStruct ("PerfAttachment", _) -> true | _ -> false) -> + | [attachment], [attachment_arg] when (match attachment.val_type with IRStruct ("perf_attachment", _) -> true | _ -> false) -> ("ks_detach_perf_attachment", [attachment_arg]) | [_], [program_handle] -> ("detach_bpf_program_by_fd", [program_handle]) @@ -4033,42 +4033,35 @@ let generate_complete_userspace_program_from_ir ?(config_declarations = []) ?(ta #include #include -/* KernelScript perf_event type tags */ -typedef enum { - perf_type_hardware = PERF_TYPE_HARDWARE, - perf_type_software = PERF_TYPE_SOFTWARE, - perf_type_tracepoint = PERF_TYPE_TRACEPOINT, - perf_type_hw_cache = PERF_TYPE_HW_CACHE, - perf_type_raw = PERF_TYPE_RAW, - perf_type_breakpoint = PERF_TYPE_BREAKPOINT -} perf_type; - -/* Common config values for PERF_TYPE_HARDWARE */ -typedef enum { - cpu_cycles = PERF_COUNT_HW_CPU_CYCLES, - instructions = PERF_COUNT_HW_INSTRUCTIONS, - cache_references = PERF_COUNT_HW_CACHE_REFERENCES, - cache_misses = PERF_COUNT_HW_CACHE_MISSES, - branch_instructions = PERF_COUNT_HW_BRANCH_INSTRUCTIONS, - branch_misses = PERF_COUNT_HW_BRANCH_MISSES -} perf_hw_config; - -/* Common config values for PERF_TYPE_SOFTWARE */ -typedef enum { - page_faults = PERF_COUNT_SW_PAGE_FAULTS, - context_switches = PERF_COUNT_SW_CONTEXT_SWITCHES, - cpu_migrations = PERF_COUNT_SW_CPU_MIGRATIONS -} perf_sw_config; - -typedef struct PerfAttachment { +/* KernelScript perf events: a single value packs the perf_event_attr.type tag + * in the high 32 bits and the config in the low 32 bits, so the two can never be + * mismatched. Keep these in sync with the perf_event enum in stdlib.ml. */ +#define KS_PERF_EVENT(type, config) (((uint64_t)(type) << 32) | (uint32_t)(config)) +#define KS_PERF_EVENT_TYPE(event) ((__u32)((uint64_t)(event) >> 32)) +#define KS_PERF_EVENT_CONFIG(event) ((__u64)((uint64_t)(event) & 0xFFFFFFFFULL)) + +/* PERF_TYPE_HARDWARE events */ +#define cpu_cycles KS_PERF_EVENT(PERF_TYPE_HARDWARE, PERF_COUNT_HW_CPU_CYCLES) +#define instructions KS_PERF_EVENT(PERF_TYPE_HARDWARE, PERF_COUNT_HW_INSTRUCTIONS) +#define cache_references KS_PERF_EVENT(PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_REFERENCES) +#define cache_misses KS_PERF_EVENT(PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_MISSES) +#define branch_instructions KS_PERF_EVENT(PERF_TYPE_HARDWARE, PERF_COUNT_HW_BRANCH_INSTRUCTIONS) +#define branch_misses KS_PERF_EVENT(PERF_TYPE_HARDWARE, PERF_COUNT_HW_BRANCH_MISSES) + +/* PERF_TYPE_SOFTWARE events */ +#define page_faults KS_PERF_EVENT(PERF_TYPE_SOFTWARE, PERF_COUNT_SW_PAGE_FAULTS) +#define context_switches KS_PERF_EVENT(PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CONTEXT_SWITCHES) +#define cpu_migrations KS_PERF_EVENT(PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CPU_MIGRATIONS) + +typedef struct perf_attachment { int perf_fd; int link_id; int prog_fd; uint64_t generation; -} PerfAttachment; +} perf_attachment; #define KS_PERF_GROUP_MAX_VALUES %d -typedef struct PerfRead { +typedef struct perf_read { int64_t raw; int64_t scaled; uint64_t time_enabled; @@ -4076,18 +4069,16 @@ typedef struct PerfRead { uint32_t count; int64_t values[KS_PERF_GROUP_MAX_VALUES]; uint64_t ids[KS_PERF_GROUP_MAX_VALUES]; -} PerfRead; +} perf_read; /* ks_perf_options holds all KernelScript perf_options fields plus the inner * kernel perf_event_attr (from linux/perf_event.h) that ks_open_perf_event fills. */ typedef struct { struct perf_event_attr attr; /* kernel perf_event_attr filled by ks_open_perf_event */ - int32_t perf_type; /* perf_event_attr.type tag */ - uint64_t perf_config; /* perf_event_attr.config value for the chosen type */ + uint64_t event; /* packed perf event: high 32 bits = type, low 32 bits = config */ int32_t pid; /* process ID (-1 = all processes, default) */ int32_t cpu; /* CPU number (0 = CPU 0, default) */ - int32_t group_fd; /* perf event group leader fd (-1 = no group, default) */ - PerfAttachment group; /* high-level group leader attachment */ + perf_attachment group; /* group leader attachment (-1 fields = no group, default) */ uint64_t period; /* sampling period (default 1 000 000) */ uint32_t wakeup; /* wakeup after N events (default 1) */ bool inherit; /* inherit to child processes (default false) */ @@ -4388,7 +4379,7 @@ void cleanup_bpf_maps(void) { entry->generation = 0; } - static struct perf_attachment_state *perf_attachment_begin_read(PerfAttachment attachment) { + static struct perf_attachment_state *perf_attachment_begin_read(perf_attachment attachment) { if (attachment.perf_fd < 0 || attachment.link_id <= 0 || attachment.generation == 0) { return NULL; } @@ -4541,7 +4532,7 @@ void cleanup_bpf_maps(void) { int group_leader_fd; // Perf group leader fd for members (-1 otherwise) int is_group_member; // Non-zero when perf_fd belongs to a group leader int detaching; // Non-zero while teardown is in progress - uint64_t generation; // PerfAttachment stale-handle token + uint64_t generation; // perf_attachment stale-handle token enum bpf_prog_type type; struct attachment_entry *next; }; @@ -4957,7 +4948,7 @@ void cleanup_bpf_maps(void) { }|} perf_leader_guard_line invalidate_call_line else "" in let perf_detach_function = if all_usage.uses_attach_perf then - {|void ks_detach_perf_attachment(PerfAttachment attachment) { + {|void ks_detach_perf_attachment(perf_attachment attachment) { if (attachment.link_id <= 0) { fprintf(stderr, "Invalid perf attachment link id: %d\n", attachment.link_id); return; @@ -5144,9 +5135,9 @@ static int ensure_bpf_dir(const char *path) { let perf_attach_function = if all_usage.uses_attach_perf then let perf_attach_template = {|int ks_open_perf_event(ks_perf_options ks_attr) { /* Fill the BTF-derived struct perf_event_attr from KernelScript fields */ - ks_attr.attr.type = (__u32)ks_attr.perf_type; + ks_attr.attr.type = KS_PERF_EVENT_TYPE(ks_attr.event); ks_attr.attr.size = sizeof(struct perf_event_attr); - ks_attr.attr.config = (__u64)ks_attr.perf_config; + ks_attr.attr.config = KS_PERF_EVENT_CONFIG(ks_attr.event); ks_attr.attr.sample_type = 0; ks_attr.attr.sample_period = ks_attr.period > 0 ? ks_attr.period : 1000000; ks_attr.attr.wakeup_events = ks_attr.wakeup > 0 ? ks_attr.wakeup : 1; @@ -5162,7 +5153,7 @@ static int ensure_bpf_dir(const char *path) { int cpu = ks_attr.cpu; int pid = ks_attr.pid; - int group_fd = ks_attr.group_fd; + int group_fd = -1; if (ks_attr.group.perf_fd >= 0 && ks_attr.group.link_id > 0 && ks_attr.group.generation != 0) { @@ -5225,8 +5216,8 @@ static int ks_restart_perf_group(int group_fd) { /* Attach a perf_event BPF program using a ks_perf_options config. * Standalone events are reset and enabled directly; group members restart their * leader group after the member link is attached. */ -PerfAttachment ks_attach_perf_event(int prog_fd, ks_perf_options opts, int flags) { - PerfAttachment attachment = { +perf_attachment ks_attach_perf_event(int prog_fd, ks_perf_options opts, int flags) { + perf_attachment attachment = { .perf_fd = -1, .link_id = -1, .prog_fd = prog_fd, @@ -5255,7 +5246,7 @@ PerfAttachment ks_attach_perf_event(int prog_fd, ks_perf_options opts, int flags int effective_group_fd = (opts.group.perf_fd >= 0 && opts.group.link_id > 0 && opts.group.generation != 0) ? opts.group.perf_fd - : opts.group_fd; + : -1; bool is_group_member = effective_group_fd >= 0; int perf_fd = ks_open_perf_event(opts); if (perf_fd < 0) return attachment; @@ -5306,8 +5297,8 @@ PerfAttachment ks_attach_perf_event(int prog_fd, ks_perf_options opts, int flags char perf_target[128]; snprintf(perf_target, sizeof(perf_target), "perf_event:type=%d config=%llu period=%llu group_fd=%d", - opts.perf_type, - (unsigned long long)opts.perf_config, + KS_PERF_EVENT_TYPE(opts.event), + (unsigned long long)KS_PERF_EVENT_CONFIG(opts.event), (unsigned long long)opts.period, effective_group_fd); @@ -5374,7 +5365,7 @@ static int64_t ks_scale_perf_count(uint64_t value, uint64_t time_enabled, uint64 return (int64_t)scaled; } -static int ks_read_perf_from_fd(int perf_fd, uint64_t event_id, PerfRead *result, const char *caller) { +static int ks_read_perf_from_fd(int perf_fd, uint64_t event_id, perf_read *result, const char *caller) { if (perf_fd < 0) { fprintf(stderr, "%s: invalid perf_fd %d\n", caller, perf_fd); return -1; @@ -5466,8 +5457,8 @@ static int ks_read_perf_from_fd(int perf_fd, uint64_t event_id, PerfRead *result } /* Read raw/scaled details and the current group snapshot for a perf attachment. */ -PerfRead ks_perf_attachment_read(PerfAttachment attachment) { - PerfRead result = { +perf_read ks_perf_attachment_read(perf_attachment attachment) { + perf_read result = { .raw = -1, .scaled = -1, .count = 0, diff --git a/tests/test_perf_event_attach.ml b/tests/test_perf_event_attach.ml index 1160d5a..f7b4460 100644 --- a/tests/test_perf_event_attach.ml +++ b/tests/test_perf_event_attach.ml @@ -38,26 +38,18 @@ let bool_value value = let int64_value value = make_ir_value (IRLiteral (IntLit (Signed64 value, None))) IRI64 test_pos -let perf_type_value name raw_value = +let perf_event_value name raw_value = make_ir_value - (IREnumConstant ("perf_type", name, Signed64 raw_value)) - (IREnum ("perf_type", [])) - test_pos - -let perf_config_value enum_name name raw_value = - make_ir_value - (IREnumConstant (enum_name, name, Signed64 raw_value)) - (IREnum (enum_name, [])) + (IREnumConstant ("perf_event", name, Signed64 raw_value)) + (IREnum ("perf_event", [])) test_pos let perf_attr_expr ~pid ~cpu = make_ir_expr (IRStructLiteral ("perf_options", [ - ("perf_type", perf_type_value "perf_type_hardware" 0L); - ("perf_config", perf_config_value "perf_hw_config" "branch_misses" 5L); + ("event", perf_event_value "branch_misses" 0x5L); ("pid", int32_value pid); ("cpu", int32_value cpu); - ("group_fd", int32_value (-1L)); ("period", uint64_value 1000000L); ("wakeup", uint32_value 1L); ("inherit", bool_value false); @@ -150,11 +142,9 @@ let appears_before str a b = let perf_attr_expr_with ~period ~wakeup = make_ir_expr (IRStructLiteral ("perf_options", [ - ("perf_type", perf_type_value "perf_type_hardware" 0L); - ("perf_config", perf_config_value "perf_hw_config" "branch_misses" 5L); + ("event", perf_event_value "branch_misses" 0x5L); ("pid", int32_value 1234L); ("cpu", int32_value 0L); - ("group_fd", int32_value (-1L)); ("period", uint64_value period); ("wakeup", uint32_value wakeup); ("inherit", bool_value false); @@ -172,7 +162,7 @@ let make_perf_code_with ~period ~wakeup = let attachment_value = make_ir_value (IRVariable "att") - (IRStruct ("PerfAttachment", [("perf_fd", IRI32); ("link_id", IRI32); ("prog_fd", IRI32); ("generation", IRU64)])) + (IRStruct ("perf_attachment", [("perf_fd", IRI32); ("link_id", IRI32); ("prog_fd", IRI32); ("generation", IRU64)])) test_pos in let attr_decl = @@ -242,12 +232,10 @@ let test_perf_event_period_and_wakeup_custom () = let test_perf_event_group_fd_codegen () = let code = make_perf_code_with ~period:1000000L ~wakeup:1L in - check bool "ks_perf_options carries group_fd" true + check bool "perf options carry no raw group_fd field" false (contains_substr code "int32_t group_fd;"); - check bool "hand-built perf options default group_fd to -1" true - (contains_substr code ".group_fd = -1"); - check bool "group_fd copied from options" true - (contains_substr code "int group_fd = ks_attr.group_fd;"); + check bool "group leader fd defaults to no-group" true + (contains_substr code "int group_fd = -1;"); check bool "invalid group_fd rejected" true (contains_substr code "if (group_fd < -1)"); check bool "perf_event_open receives variable group_fd" true @@ -323,10 +311,10 @@ let test_read_helpers_generated_when_used () = let attachment_value = make_ir_value (IRVariable "att") - (IRStruct ("PerfAttachment", [("perf_fd", IRI32); ("link_id", IRI32); ("prog_fd", IRI32); ("generation", IRU64)])) + (IRStruct ("perf_attachment", [("perf_fd", IRI32); ("link_id", IRI32); ("prog_fd", IRI32); ("generation", IRU64)])) test_pos in - let count_value = make_ir_value (IRVariable "count") (IRStruct ("PerfRead", [])) test_pos in + let count_value = make_ir_value (IRVariable "count") (IRStruct ("perf_read", [])) test_pos in let attr_decl = make_ir_instruction (IRVariableDecl (attr_value, IRStruct ("perf_options", []), @@ -364,10 +352,10 @@ let test_perf_read_helper_scales_multiplexed_counts () = let attachment_value = make_ir_value (IRVariable "att") - (IRStruct ("PerfAttachment", [("perf_fd", IRI32); ("link_id", IRI32); ("prog_fd", IRI32); ("generation", IRU64)])) + (IRStruct ("perf_attachment", [("perf_fd", IRI32); ("link_id", IRI32); ("prog_fd", IRI32); ("generation", IRU64)])) test_pos in - let count_value = make_ir_value (IRVariable "count") (IRStruct ("PerfRead", [])) test_pos in + let count_value = make_ir_value (IRVariable "count") (IRStruct ("perf_read", [])) test_pos in let attr_decl = make_ir_instruction (IRVariableDecl (attr_value, IRStruct ("perf_options", []), @@ -421,9 +409,9 @@ let test_perf_attach_event_function_generated () = check bool "no snprintf perf_fd string hack" false (contains_substr code "snprintf(%s, sizeof(%s),"); check bool "perf attr type copied directly from perf_options" true - (contains_substr code "ks_attr.attr.type = (__u32)ks_attr.perf_type;"); + (contains_substr code "ks_attr.attr.type = KS_PERF_EVENT_TYPE(ks_attr.event);"); check bool "perf attr config copied directly from perf_options" true - (contains_substr code "ks_attr.attr.config = (__u64)ks_attr.perf_config;"); + (contains_substr code "ks_attr.attr.config = KS_PERF_EVENT_CONFIG(ks_attr.event);"); check bool "old perf_counter switch removed" false (contains_substr code "switch (ks_attr.counter)"); check bool "find_prog_by_fd helper used for program lookup" true @@ -434,11 +422,11 @@ let test_perf_attach_event_function_generated () = (contains_substr code "perf attach flags must be 0"); check bool "perf attach no longer ignores flags" false (contains_substr code "(void)flags"); - check bool "perf attach returns PerfAttachment" true - (contains_substr code "PerfAttachment ks_attach_perf_event"); + check bool "perf attach returns perf_attachment" true + (contains_substr code "perf_attachment ks_attach_perf_event"); check bool "attachment struct typedef emitted" true - (contains_substr code "typedef struct PerfAttachment"); - check bool "PerfAttachment carries stale-handle generation" true + (contains_substr code "typedef struct perf_attachment"); + check bool "perf_attachment carries stale-handle generation" true (contains_substr code "uint64_t generation;"); check bool "perf attach records kernel perf event id" true (contains_substr code "PERF_EVENT_IOC_ID"); @@ -460,7 +448,7 @@ let test_detach_attach_concurrent_window () = let attachment_value = make_ir_value (IRVariable "att") - (IRStruct ("PerfAttachment", [("perf_fd", IRI32); ("link_id", IRI32); ("prog_fd", IRI32); ("generation", IRU64)])) + (IRStruct ("perf_attachment", [("perf_fd", IRI32); ("link_id", IRI32); ("prog_fd", IRI32); ("generation", IRU64)])) test_pos in let attr_decl = @@ -493,40 +481,6 @@ let test_detach_attach_concurrent_window () = check bool "detach invalidates stale perf attachment handles before close" true (contains_substr code "invalidate_perf_attachment_state_locked(entry)") -let test_perf_group_source_field_access_codegen () = - let source = {| -@perf_event -fn on_event(ctx: *bpf_perf_event_data) -> i32 { - return 0 -} - -fn main() -> i32 { - var prog = load(on_event) - var cache = attach(prog, perf_options { - perf_type: perf_type_hardware, - perf_config: cache_misses, - }, 0) - var branch = attach(prog, perf_options { - perf_type: perf_type_hardware, - perf_config: branch_misses, - group_fd: cache.perf_fd, - }, 0) - detach(branch) - detach(cache) - detach(prog) - return 0 -} -|} in - let code = make_generated_code_from_source source in - check bool "source group_fd field access type-checks and codegens" true - (contains_substr code "var_cache.perf_fd"); - check bool "source emits grouped perf option assignment" true - (contains_substr code ".group_fd = __field_access_"); - check bool "leader detach protection helper generated" true - (contains_substr code "perf_group_has_active_members_locked"); - check bool "detach cascades active group leaders" true - (contains_substr code "Detaching perf group leader fd %d cascades to %d active member(s)") - let test_perf_group_attachment_field_codegen () = let source = {| @perf_event @@ -537,12 +491,10 @@ fn on_event(ctx: *bpf_perf_event_data) -> i32 { fn main() -> i32 { var prog = load(on_event) var cache = attach(prog, perf_options { - perf_type: perf_type_hardware, - perf_config: cache_misses, + event: cache_misses, }, 0) var branch = attach(prog, perf_options { - perf_type: perf_type_hardware, - perf_config: branch_misses, + event: branch_misses, group: cache, }, 0) detach(branch) @@ -553,11 +505,15 @@ fn main() -> i32 { |} in let code = make_generated_code_from_source source in check bool "perf_options carries high-level group attachment" true - (contains_substr code "PerfAttachment group;"); + (contains_substr code "perf_attachment group;"); check bool "source group attachment field type-checks and codegens" true (contains_substr code ".group = var_cache"); check bool "runtime prefers valid group attachment fd" true - (contains_substr code "opts.group.perf_fd >= 0 && opts.group.link_id > 0 && opts.group.generation != 0") + (contains_substr code "opts.group.perf_fd >= 0 && opts.group.link_id > 0 && opts.group.generation != 0"); + check bool "leader detach protection helper generated" true + (contains_substr code "perf_group_has_active_members_locked"); + check bool "detach cascades active group leaders" true + (contains_substr code "Detaching perf group leader fd %d cascades to %d active member(s)") let test_perf_read_codegen () = let source = {| @@ -569,12 +525,10 @@ fn on_event(ctx: *bpf_perf_event_data) -> i32 { fn main() -> i32 { var prog = load(on_event) var cache = attach(prog, perf_options { - perf_type: perf_type_hardware, - perf_config: cache_misses, + event: cache_misses, }, 0) var branch = attach(prog, perf_options { - perf_type: perf_type_hardware, - perf_config: branch_misses, + event: branch_misses, group: cache, }, 0) var snapshot = read(cache) @@ -594,7 +548,7 @@ fn main() -> i32 { |} in let code = make_generated_code_from_source source in check bool "unified read helper generated" true - (contains_substr code "PerfRead ks_perf_attachment_read"); + (contains_substr code "perf_read ks_perf_attachment_read"); check bool "old raw helper removed" false (contains_substr code "ks_perf_attachment_read_raw"); check bool "old details helper removed" false @@ -615,139 +569,6 @@ fn main() -> i32 { check bool "array snapshot indexing dereferences element pointer" true (contains_substr code "*__array_ptr_") -let test_perf_group_too_large_static_group_rejected () = - Unix.putenv "KERNELSCRIPT_PERF_GROUP_MAX_EVENTS" "4"; - let source = {| -@perf_event -fn on_event(ctx: *bpf_perf_event_data) -> i32 { - return 0 -} - -fn main() -> i32 { - var prog = load(on_event) - var cache = attach(prog, perf_options { - perf_type: perf_type_hardware, - perf_config: cache_misses, - }, 0) - var branch = attach(prog, perf_options { - perf_type: perf_type_hardware, - perf_config: branch_misses, - group: cache, - }, 0) - var cycles = attach(prog, perf_options { - perf_type: perf_type_hardware, - perf_config: cpu_cycles, - group: cache, - }, 0) - var inst = attach(prog, perf_options { - perf_type: perf_type_hardware, - perf_config: instructions, - group: cache, - }, 0) - var refs = attach(prog, perf_options { - perf_type: perf_type_hardware, - perf_config: cache_references, - group: cache, - }, 0) - detach(refs) - detach(inst) - detach(cycles) - detach(branch) - detach(cache) - detach(prog) - return 0 -} -|} in - try - let _ = make_generated_code_from_source source in - fail "Oversized static perf event group should be rejected at compile time" - with - | Type_error (msg, _) -> - check bool "oversized group reports PMU group limit" true - (contains_substr msg "perf event group rooted at 'cache' needs 5 PMU counter slot(s), but target PMU group limit is 4") - | exn -> - fail ("Expected Type_error for oversized perf event group, got " ^ Printexc.to_string exn) - -let test_perf_group_too_many_static_members_rejected () = - Unix.putenv "KERNELSCRIPT_PERF_GROUP_MAX_EVENTS" "32"; - let member_decls = - List.init 16 (fun i -> - Printf.sprintf {| - var sw%d = attach(prog, perf_options { - perf_type: perf_type_software, - perf_config: context_switches, - group: leader, - }, 0)|} i) - |> String.concat "\n" - in - let source = {| -@perf_event -fn on_event(ctx: *bpf_perf_event_data) -> i32 { - return 0 -} - -fn main() -> i32 { - var prog = load(on_event) - var leader = attach(prog, perf_options { - perf_type: perf_type_software, - perf_config: page_faults, - }, 0) -|} ^ member_decls ^ {| - detach(leader) - detach(prog) - return 0 -} -|} in - try - let _ = make_generated_code_from_source source in - fail "Static perf event group with more than 16 members should be rejected" - with - | Type_error (msg, _) -> - check bool "oversized group reports clamped perf group limit" true - (contains_substr msg "perf event group rooted at 'leader' has 17 member(s), but target perf group limit is 16") - | exn -> - fail ("Expected Type_error for oversized perf event member count, got " ^ Printexc.to_string exn) - -let test_perf_group_env_override_clamped_to_read_capacity () = - Unix.putenv "KERNELSCRIPT_PERF_GROUP_MAX_EVENTS" "32"; - let member_decls = - List.init 16 (fun i -> - Printf.sprintf {| - var hw%d = attach(prog, perf_options { - perf_type: perf_type_hardware, - perf_config: branch_misses, - group: leader, - }, 0)|} i) - |> String.concat "\n" - in - let source = {| -@perf_event -fn on_event(ctx: *bpf_perf_event_data) -> i32 { - return 0 -} - -fn main() -> i32 { - var prog = load(on_event) - var leader = attach(prog, perf_options { - perf_type: perf_type_hardware, - perf_config: cache_misses, - }, 0) -|} ^ member_decls ^ {| - detach(leader) - detach(prog) - return 0 -} -|} in - try - let _ = make_generated_code_from_source source in - fail "Perf group limit override above PerfRead capacity should be clamped" - with - | Type_error (msg, _) -> - check bool "oversized group reports clamped PMU group limit" true - (contains_substr msg "perf event group rooted at 'leader' needs 17 PMU counter slot(s), but target PMU group limit is 16") - | exn -> - fail ("Expected Type_error for clamped perf event group limit, got " ^ Printexc.to_string exn) - (* ── Type-checking regression tests ───────────────────────────────────── *) let parse_and_check source = @@ -823,12 +644,8 @@ let tests = [ test_case "perf_read_helper_scales_multiplexed_counts"`Quick test_perf_read_helper_scales_multiplexed_counts; test_case "perf_attach_event_function_generated" `Quick test_perf_attach_event_function_generated; test_case "detach_attach_concurrent_window" `Quick test_detach_attach_concurrent_window; - test_case "perf_group_source_field_access_codegen" `Quick test_perf_group_source_field_access_codegen; test_case "perf_group_attachment_field_codegen" `Quick test_perf_group_attachment_field_codegen; test_case "perf_read_codegen" `Quick test_perf_read_codegen; - test_case "perf_group_too_large_static_group_rejected" `Quick test_perf_group_too_large_static_group_rejected; - test_case "perf_group_too_many_static_members_rejected" `Quick test_perf_group_too_many_static_members_rejected; - test_case "perf_group_env_override_clamped_to_read_capacity" `Quick test_perf_group_env_override_clamped_to_read_capacity; test_case "standard_attach_uses_libbpf_error_checks" `Quick test_standard_attach_uses_libbpf_error_checks; ] diff --git a/tests/test_program_ref.ml b/tests/test_program_ref.ml index e04e721..cfc5e72 100644 --- a/tests/test_program_ref.ml +++ b/tests/test_program_ref.ml @@ -151,7 +151,7 @@ let test_stdlib_integration () = (match Kernelscript.Stdlib.get_builtin_function_signature "read" with | Some (params, return_type) -> check int "read parameter count" 0 (List.length params); - check bool "read return type is PerfRead" true (return_type = Kernelscript.Ast.Struct "PerfRead") + check bool "read return type is perf_read" true (return_type = Kernelscript.Ast.Struct "perf_read") | None -> check bool "read function signature should exist" false true); (* Verify that the custom validation function is wired up on the attach entry *) @@ -171,8 +171,7 @@ let test_perf_attachment_value_flow () = fn main() -> i32 { var prog = load(on_cache_miss) var att = attach(prog, perf_options { - perf_type: perf_type_hardware, - perf_config: cache_misses, + event: cache_misses, period: 1000000, }, 0) var snapshot = read(att)