Skip to content

Commit 7044637

Browse files
committed
docs: document if (var x = expr) and m[k].field op= rhs in README and SPEC
Signed-off-by: Cong Wang <cwang@multikernel.io>
1 parent ca701a7 commit 7044637

2 files changed

Lines changed: 181 additions & 12 deletions

File tree

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ KernelScript addresses these problems through revolutionary language features:
6262

6363
**Zero-boilerplate shared state** - Maps are automatically accessible across all programs as regular global variables in a programming language
6464

65+
**Ergonomic map idioms** - Declaration-as-condition (`if (var s = m[k]) { s.field = ... }`) and compound assignment on map indices (`m[k].count += 1`) compile down to a single presence-checked lookup with in-place mutation, no manual write-back
66+
6567
**Builtin kfunc support** - Define full-privilege kernel functions that eBPF programs can call directly, automatically generating kernel modules and BTF registrations
6668

6769
**Unified error handling** - C-style integer throw/catch works seamlessly in both eBPF and userspace contexts, unlike complex Result types
@@ -223,6 +225,27 @@ fn lookup_or_create(ip: IpAddress) -> Counter {
223225
return 1
224226
}
225227
}
228+
229+
// Declaration-as-condition: bind only inside the truthy branch.
230+
// For struct-valued maps, the bound name is the lookup pointer, so
231+
// field access auto-derefs and the generated eBPF performs in-place
232+
// mutation against the underlying entry — no write-back needed.
233+
pin var ip_stats : hash<IpAddress, PacketInfo>(1024)
234+
235+
@helper
236+
fn record_packet(ip: IpAddress, size: PacketSize) {
237+
if (var stats = ip_stats[ip]) {
238+
stats.size = size
239+
} else {
240+
ip_stats[ip] = PacketInfo { src_ip: ip, dst_ip: 0, protocol: 0, size: size }
241+
}
242+
}
243+
244+
// Compound assignment indexes into struct-valued maps directly:
245+
@helper
246+
fn bump_size(ip: IpAddress, delta: PacketSize) {
247+
ip_stats[ip].size += delta // emits a presence-checked ptr->size += delta
248+
}
226249
```
227250

228251
### Multi-Program Coordination

SPEC.md

Lines changed: 158 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2769,12 +2769,10 @@ var flow_stats : hash<u32, FlowStats>(1024)
27692769
27702770
@helper
27712771
fn update_flow_stats(flow_id: u32, packet_size: u32) {
2772-
var stats = flow_stats[flow_id]
2773-
if (stats != null) {
2774-
stats.packet_count += 1
2775-
stats.total_bytes += packet_size
2776-
stats.avg_packet_size = stats.total_bytes / stats.packet_count
2777-
}
2772+
// Compound assignment on a struct-field of a map value emits a single
2773+
// presence-checked map lookup and mutates in place; see §6.2.5.
2774+
flow_stats[flow_id].packet_count += 1
2775+
flow_stats[flow_id].total_bytes += packet_size
27782776
}
27792777
```
27802778

@@ -2825,7 +2823,73 @@ fn main() -> i32 {
28252823
}
28262824
```
28272825

2828-
#### 6.2.5 Performance and Code Generation
2826+
#### 6.2.5 Compound Assignment with Map Indexing
2827+
2828+
KernelScript extends compound assignment to map index expressions, so a
2829+
counter update against a map value can be written without an intermediate
2830+
variable or an explicit write-back.
2831+
2832+
##### 6.2.5.1 Scalar map values
2833+
2834+
When the map's value type is an integer, `m[k] op= rhs` reads the current
2835+
entry, applies `op`, and writes the result back. If the entry is absent
2836+
the read yields zero, so the operation creates the entry on first use.
2837+
2838+
```kernelscript
2839+
var packet_counts : hash<u32, u64>(1024)
2840+
2841+
@xdp
2842+
fn rate_limiter(ctx: *xdp_md) -> xdp_action {
2843+
var src_ip = extract_src_ip(ctx)
2844+
packet_counts[src_ip] += 1 // read-modify-write; creates entry if absent
2845+
return XDP_PASS
2846+
}
2847+
```
2848+
2849+
The supported operators are `+=`, `-=`, `*=`, `/=`, `%=`. The map's value
2850+
type must be one of the integer primitives.
2851+
2852+
##### 6.2.5.2 Struct-field map values
2853+
2854+
When the map's value type is a struct, `m[k].field op= rhs` mutates a
2855+
single field of an existing entry in place. The compiler lowers the form
2856+
to a presence-checked pointer mutation:
2857+
2858+
```kernelscript
2859+
struct PacketStats {
2860+
count: u64,
2861+
total_bytes: u64,
2862+
}
2863+
2864+
var ip_stats : hash<u32, PacketStats>(1024)
2865+
2866+
@xdp
2867+
fn observe(ctx: *xdp_md) -> xdp_action {
2868+
var ip = extract_src_ip(ctx)
2869+
var len = packet_len(ctx)
2870+
ip_stats[ip].count += 1
2871+
ip_stats[ip].total_bytes += len
2872+
return XDP_PASS
2873+
}
2874+
```
2875+
2876+
Semantics:
2877+
2878+
- **Map identifier required.** The left-hand side must be `IDENT[expr].field op= rhs`;
2879+
arbitrary LHS expressions are not allowed.
2880+
- **Value type must be a struct.** `field` is resolved against the map's value
2881+
struct definition; an unknown field is a compile-time error.
2882+
- **Field type drives `op`.** The named field must be one of the integer
2883+
primitives; the right-hand side must be assignment-compatible with the field type.
2884+
- **Presence check, no creation.** If the entry is absent the statement is a
2885+
no-op — unlike scalar `m[k] op= rhs`, the struct-field form does *not*
2886+
create a default entry. To handle the missing case, pair it with an
2887+
explicit `else` using the declaration-as-condition form (see §7.5.1).
2888+
- **Single map lookup.** Generated code performs one `bpf_map_lookup_elem`,
2889+
guards on the returned pointer, and writes through it
2890+
(`if (p) { p->field = p->field op rhs; }`); there is no separate write-back.
2891+
2892+
#### 6.2.6 Performance and Code Generation
28292893

28302894
Compound assignments generate efficient code in both contexts:
28312895

@@ -3220,6 +3284,13 @@ fn main(args: Args) -> i32 {
32203284
### 7.5 Control Flow Statements
32213285

32223286
#### 7.5.1 Conditional Statements
3287+
3288+
KernelScript provides two `if` forms: a standard expression-condition
3289+
form and a *declaration-as-condition* form that combines a single-use
3290+
binding with a presence check.
3291+
3292+
##### 7.5.1.1 Expression-condition form
3293+
32233294
```kernelscript
32243295
// Conditional statements
32253296
if (condition) {
@@ -3231,6 +3302,48 @@ if (condition) {
32313302
}
32323303
```
32333304

3305+
##### 7.5.1.2 Declaration-as-condition form (`if (var name = expr)`)
3306+
3307+
```kernelscript
3308+
if (var name = expr) {
3309+
// then-branch: `name` is in scope and bound to `expr`'s value
3310+
} else {
3311+
// else-branch: `name` is *not* in scope
3312+
}
3313+
```
3314+
3315+
The branch is taken iff `expr` produces a *present* value:
3316+
3317+
- **Map index** (`m[k]`): present iff the entry exists. The bound name is
3318+
the lookup pointer, so field access auto-derefs and field assignments
3319+
mutate the underlying map entry in place — no explicit write-back is
3320+
needed:
3321+
3322+
```kernelscript
3323+
if (var stats = ip_stats[ip]) {
3324+
stats.count = stats.count + 1 // writes through the lookup pointer
3325+
} else {
3326+
ip_stats[ip] = PacketStats { count: 1, total_bytes: 0 }
3327+
}
3328+
```
3329+
3330+
- **Pointer-returning expression**: present iff non-null. Useful with
3331+
helpers and kfuncs that may return `null`.
3332+
3333+
Semantics:
3334+
3335+
- **Single evaluation.** `expr` is evaluated exactly once; its presence
3336+
test guards both branches.
3337+
- **Scoping.** `name` is in scope only inside the then-branch. Referencing
3338+
it from the else-branch (or after the `if`) is a compile-time error.
3339+
- **No reassignment.** `name` shadows nothing visible to the else-branch
3340+
and may shadow an outer binding only inside the then-branch.
3341+
- **Else is optional.** As with the expression-condition form, the
3342+
`else` branch may be omitted.
3343+
- **Lowering.** The form lowers to a single `bpf_map_lookup_elem` (or the
3344+
underlying pointer-returning call), a null check, and the chosen
3345+
branch — there is no second lookup.
3346+
32343347
#### 7.5.2 Match Expressions
32353348

32363349
KernelScript provides `match` expressions for efficient multi-way branching. Match is an expression that returns a value and can be used anywhere an expression is expected.
@@ -4303,14 +4416,47 @@ statement = expression_statement | assignment_statement | declaration_statement
43034416
try_statement | throw_statement | defer_statement
43044417
43054418
expression_statement = expression
4306-
assignment_statement = identifier assignment_operator expression
4307-
assignment_operator = "=" | "+=" | "-=" | "*=" | "/=" | "%="
4419+
4420+
assignment_statement = simple_assignment | compound_assignment | field_assignment |
4421+
arrow_assignment | index_assignment | compound_index_assignment |
4422+
compound_field_index_assignment
4423+
4424+
simple_assignment = identifier "=" expression (* x = e *)
4425+
compound_assignment = identifier compound_operator expression (* x op= e *)
4426+
field_assignment = primary_expression "." identifier "=" expression (* o.field = e *)
4427+
arrow_assignment = primary_expression "->" identifier "=" expression (* p->field = e *)
4428+
index_assignment = expression "[" expression "]" "=" expression (* m[k] = e *)
4429+
compound_index_assignment = expression "[" expression "]" compound_operator expression
4430+
(* m[k] op= e:
4431+
scalar map values; reads, applies op, writes back;
4432+
absent entries read as 0, so the form creates an
4433+
entry on first use. See §6.2.5.1. *)
4434+
compound_field_index_assignment = identifier "[" expression "]" "." identifier compound_operator expression
4435+
(* m[k].field op= e:
4436+
struct-valued map; lowers to a single
4437+
bpf_map_lookup_elem + null-checked
4438+
ptr->field op= e; absent entries are a no-op
4439+
(no entry is created). See §6.2.5.2. *)
4440+
4441+
assignment_operator = "=" | compound_operator
4442+
compound_operator = "+=" | "-=" | "*=" | "/=" | "%="
43084443
43094444
declaration_statement = "var" identifier [ ":" type_annotation ] "=" expression
43104445
4311-
if_statement = "if" "(" expression ")" "{" statement_list "}"
4312-
{ "else" "if" "(" expression ")" "{" statement_list "}" }
4313-
[ "else" "{" statement_list "}" ]
4446+
if_statement = expression_if | iflet_if
4447+
4448+
expression_if = "if" "(" expression ")" "{" statement_list "}"
4449+
{ "else" "if" "(" expression ")" "{" statement_list "}" }
4450+
[ "else" "{" statement_list "}" ]
4451+
4452+
iflet_if = "if" "(" "var" identifier "=" expression ")" "{" statement_list "}"
4453+
[ "else" ( "{" statement_list "}" | iflet_if | expression_if ) ]
4454+
(* Declaration-as-condition: the right-hand side is evaluated once;
4455+
the then-branch is taken iff the value is *present* (a map hit
4456+
or a non-null pointer). `identifier` is bound only inside the
4457+
then-branch. For map-index right-hand sides the binding is the
4458+
lookup pointer (field access auto-derefs, field writes mutate
4459+
the underlying entry in place). See §7.5.1.2. *)
43144460
43154461
for_statement = "for" "(" identifier "in" expression ".." expression ")" "{" statement_list "}" |
43164462
"for" "(" identifier "," identifier ")" "in" expression "{" statement_list "}"

0 commit comments

Comments
 (0)