Repository navigation
Keep a subroutine with no written lifetime static on the compiled tier - #970
philipaxer wants to merge 2 commits into
Conversation
…r (§6.21, §13.3.1, §13.3.2) The compiled tier no longer inlines a task or void function with no written lifetime when it has locals or non-input formals. Such a subroutine is static, so its variables keep their values between calls. The call stays interpreted.
… §13.3.1, §13.3.2) Static locals and output formals under a clocked process: module tasks, void functions, block tasks bare and by path, generate-for and nested blocks in an instance. Automatic tasks and automatic modules are the contrast. A function with a result and a task without locals already worked and only hold that.
|
Integrated by cherry-pick with @philipaxer's authorship preserved: 963c16b6 and the tests 2032df19. Follow-up 22fcf628, dd8d7f29: with the change, every static task or void function with a local dropped off the compiled path. A clocked loop calling such a task went from 7.18 G to 199.8 G instructions. A static subroutine is now inlined unless a local or output formal can carry a value from one call to the next, and that loop is back at 7.18 G. The §13.3.1 behaviour is unchanged: a local read before it is written keeps its value. Published in xezim 603c9bf3 with xezim-core 212dde0. |
xezim only, based on main; the core pin is unchanged. Found while working on #952.
try_inline_task_argsonly refused an explicitstatic. A task or void function with no written lifetime is static too (§6.21, §13.3.1), and its variables retain their values between invocations (§13.3.2). Inlining gave them fresh registers on every call. Such a subroutine now stays interpreted when it has locals or non-input formals.foreigninbytecode.rsis unchanged. A body inu.gcalled fromuprints a different%m, so its%mfallback must not be emitted inline.Results
p26)1055a09)1055x10)10 55 1010 55 55Expected values come from the LRM. Verilator 5.055 agrees, except that an output formal of a static task does not retain its value there (
1for10).Suite before the rebase onto main: 6308 passed, 0 failed, 1 ignored (6297 / 0 / 1 before; 11 new tests, two of which hold behaviour that already worked).
After the rebase onto main, the 11 new tests (
--test lrm implicit_static_subroutines_keep_their_variables_when_compiled) pass. CI runs the full suite.Cost
Automatic tasks are unaffected (three benchmarks within 0.2%). A hot scalar task with no written lifetime and a loop local or output formal costs 7.65x and 4.65x in instructions, because the call goes back to the interpreter.
A more typical shape, two static tasks with
integerloop locals and an array local called from a clockedalways, costs 1.35x in instructions and 1.38x in wall time. No existing benchmark exercises this path.A narrower guard that keeps inlining where fresh registers cannot be observed is planned as a follow-up.
Not covered
t.ireadsx(§13.3.1). An explicitstaticlocal reads back.1 2where2 2is right (§13.3.2).10for55.automaticmodule prints10for55.The series
This PR is one part of a stacked series for #952. This section is the same in every part, so each PR can be read on its own.
Problem. A generate block is a scope (§23.9, §27.5, §27.6), but xezim resolved names in generate blocks by string. The elaborator renamed declarations textually (
f→g.f) from per-block name lists, and the simulator retried a missed name by its last segment at run time. Each fix to that scheme, including the one for #897, broke other legal programs: a local hiding a block function was renamed anyway, items outside the copied list kept the bare name, two unlabelled blocks with the same variable collided, a child instance's call reached the wrong parent, anddisable g.tmatched by leaf.Approach.
scope_tree.rs).elaborate/bind.rs).g.f,u.g[1].x,genblk2.t) are the keys the runtime already uses, so xezim needs almost no source change.XEZIM_BIND_AUDIT=1prints each binding next to what the rename did, so every disagreement can be listed.Parts (core / xezim; each depends on the one before and repeats its commits until that merges):
genblk<n>, §27.6 zero padding, §27.5 direct nesting) come from itdisableby path and by label is bound like other names (§9.6.2)genblk<n>Independent of the series: #969 (time scope and clocking of a block in an instance) and #970 (a subroutine with no written lifetime stays static on the compiled tier).
Still to come.
The run-time retries by leaf name stay. They serve shapes the binder cannot decide before the run (a class method calling its instance's task,
g[k].f()with a variable index, virtual interfaces) and name resolution outside generate blocks (interface ports,$root, packages).Size so far (parts 1 to 13 against main): core +4,476/-1,066 outside tests (net +3,410), +2,458 tests; xezim +198/-101 outside tests, mostly the core pin, +3,550 tests.
Testing. Every part passed the full xezim suite and the core suite before the rebase onto main. After the rebase, part 7 passed the full suites on main, and parts 8 to 13 passed the
hierarchyand core suites. CI runs the rest. Each part's audit and artifact identity against the previous part are in its own text. Expected values come from IEEE 1800-2023 and are checked against Verilator 5.055 where it accepts the program.Review order. Parts 1 and 2 are the base; the rest read from them. Reviewing those two first settles the direction.