Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
ce3fd0a
A derived-type object allocated many components at once, sized by itself
chenyueqi Sep 4, 2026
7cf2c93
A bound may name any object's component; a sibling's buffer OUT is re…
chenyueqi Sep 4, 2026
19b5b2d
Following generics into every specific, dummy-sized allocations, priv…
chenyueqi Sep 4, 2026
a5b2391
A generic is followed into the specifics its arity fits, and the recu…
chenyueqi Sep 4, 2026
76281d8
A quotient of real parameters is a real quotient; an intent(out) obje…
chenyueqi Sep 4, 2026
bff9cb4
A loop index read after its loop has Fortran's completion value
chenyueqi Sep 4, 2026
2ffbee5
SUM accumulates in element order
chenyueqi Sep 4, 2026
13e00d2
harvest's annotation says what it returns
chenyueqi Sep 4, 2026
cd1c977
The completion value only where the index is read before it is redefi…
chenyueqi Sep 4, 2026
63c0036
What the tier-2 plans needed: uncarried strings, listed state, extent…
chenyueqi Sep 4, 2026
473afa3
The recorder declares the patch count once when the probe already tak…
chenyueqi Sep 4, 2026
a114859
A call continued with trailing comments is probed whole
chenyueqi Sep 4, 2026
a67d712
The recorder guards a component the run may not allocate
chenyueqi Sep 4, 2026
9d482bf
A sibling's generic resolves its positions by arity; a stubbed call r…
chenyueqi Sep 4, 2026
34568fb
The stub rule covers the stub module's procedures, on both sides
chenyueqi Sep 4, 2026
5d876f2
A sibling's INOUT actual is read; a specific fits with its optionals …
chenyueqi Sep 4, 2026
79a61c7
A keyword actual into a sibling's procedure lands on its own position
chenyueqi Sep 4, 2026
27ebf1a
Tier-2 replay: logicals and zero-extent records, plain OUT dummies, r…
chenyueqi Sep 4, 2026
4d3ac1a
An optional OUT handed on carries the caller's presence
chenyueqi Sep 4, 2026
866743e
An optional handed on to an optional OUT is read for its presence
chenyueqi Sep 4, 2026
b852160
Module state handed whole to a function is carried; a constant expres…
chenyueqi Sep 4, 2026
5461d36
A subroutine with no OUT argument returns zero values, not one
chenyueqi Sep 4, 2026
4f64250
A declared-ungated subprogram is not compared on a recording either
chenyueqi Sep 4, 2026
c34e6c9
Re-record the corpus baseline on this branch
chenyueqi Sep 4, 2026
9f27320
Merge tier0-gaps (with main) into tier1-grid
chenyueqi Sep 4, 2026
f254155
Re-record the corpus baseline after the merge with main
chenyueqi Sep 4, 2026
59e0fe8
A probe spans blank and comment lines inside a continued call
chenyueqi Sep 4, 2026
cc91f0a
A generic function reference is ranked by the specific it dispatches to
chenyueqi Sep 4, 2026
59faf7a
JAX lowering: a CYCLE folds into the branch structure; an EXIT or a l…
chenyueqi Sep 4, 2026
b2fba33
Annotate the folded statement list (mypy)
chenyueqi Sep 4, 2026
bc1560d
JAX runtime: sqrt, ordered SUM, erf and erfc
chenyueqi Sep 4, 2026
2d4cf25
Sort the JAX runtime's export list (ruff)
chenyueqi Sep 4, 2026
428968d
JAX lowering: a loop over a static empty trip count is skipped, not t…
chenyueqi Sep 4, 2026
b1afef8
Tolerance gate: a zero-extent output has no dominant value to weigh
chenyueqi Sep 4, 2026
956fa86
A flat kernel spells the optional dummy the plan leaves out
chenyueqi Sep 4, 2026
614f0c5
JAX lowering: a loop carries only names bound before it; subscripted …
chenyueqi Sep 4, 2026
2f2aec6
Rename the tuple-store index (mypy)
chenyueqi Sep 4, 2026
89f16a2
JAX lowering: bound names come from what was emitted; a NumPy integer…
chenyueqi Sep 4, 2026
41d9f34
Annotate the lowered statement list (mypy)
chenyueqi Sep 4, 2026
f01cd80
JAX port: a bare call statement binds the kernel's returned buffer ou…
chenyueqi Sep 4, 2026
f9ba9e5
Type the bound statement (mypy)
chenyueqi Sep 4, 2026
d8837d6
JAX port: an elemental call of a companion procedure broadcasts its k…
chenyueqi Sep 4, 2026
1c84d38
Type the elemental callee's owner (mypy)
chenyueqi Sep 4, 2026
347f05b
JAX lowering: a branch on a static scalar argument is a trace-time if
chenyueqi Sep 4, 2026
19624d4
JAX lowering: a branch on a static scalar is a Python if when concret…
chenyueqi Sep 4, 2026
781dde2
Type the Python form of the branch (mypy)
chenyueqi Sep 4, 2026
e772aa4
JAX runtime: an elemental call maps the kernel over the elements in s…
chenyueqi Sep 4, 2026
756e074
JAX port: early returns of the same tuple fold into the branch structure
chenyueqi Sep 4, 2026
86055fa
JAX port: an if whose body is empty keeps its else under the inverted…
chenyueqi Sep 4, 2026
6b1b063
Narrow the return values before comparing them (mypy)
chenyueqi Sep 4, 2026
62a97ab
JAX port: an early return inside a loop becomes a flag
chenyueqi Sep 4, 2026
9f55bd5
The return flag's guards: decide before the rewrite takes the returns…
chenyueqi Sep 4, 2026
7f9b30c
JAX port: the anchor's unpack of an elided call buffer is bound, not …
chenyueqi Sep 5, 2026
836b525
The elided call buffer keeps one slot per output the callee returns
chenyueqi Sep 5, 2026
98e6392
JAX port: a companion procedure the port left on the host stays under…
chenyueqi Sep 5, 2026
9f0f019
The guarded host-call test guards on a static argument, as CLUBB's st…
chenyueqi Sep 5, 2026
ea1a842
JAX port: a call statement is not an inert log line
chenyueqi Sep 5, 2026
078256c
JAX port: a flat companion the port left on the host stays under its …
chenyueqi Sep 5, 2026
2618467
Call the object-check fixture's flat kernel by its signature
chenyueqi Sep 5, 2026
675ece9
A host-kept call releases the anchor's result buffer to the host
chenyueqi Sep 5, 2026
ebaecc2
The elided call buffer has a slot for every OUT/INOUT dummy, optional…
chenyueqi Sep 5, 2026
0be7d71
The optional-object unpack test expects the flat rule: an optional du…
chenyueqi Sep 5, 2026
4f3afbf
JAX lowering: a branch of bare call statements carries nothing
chenyueqi Sep 5, 2026
b9dfc3c
jax: guard the whole remainder after a return as one block
chenyueqi Sep 5, 2026
d7ded89
jax: rebuild a dummy object handed whole to the host at kernel entry
chenyueqi Sep 5, 2026
18c6303
jax: six rules the whole-step port needed
chenyueqi Sep 5, 2026
d4b732e
jax: EXIT as a carried flag, for-else, own subprograms kept on the host
chenyueqi Sep 5, 2026
60d596c
jax: a window with traced bounds a static distance apart is a gather
chenyueqi Sep 5, 2026
162a507
jax: an array passed through a reshape comes back in its own shape
chenyueqi Sep 5, 2026
7d6c987
jax: a static branch whose arms lowered to nothing is no branch
chenyueqi Sep 5, 2026
423187f
jax: a module constant through its alias is static
chenyueqi Sep 5, 2026
316f36f
tests: the dispatch test asserts the dual form, not the absence of it…
chenyueqi Sep 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 36 additions & 24 deletions corpus/baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -1367,11 +1367,11 @@
"stopped_by": "static.rwset",
"verdicts": {
"static.rwset": {
"detail": "7/66 blocks disagree: cspl_interp/B002, cspleps/B003, csplint/B006, csplint/B012, csplint/B016 (+2 more)",
"detail": "5/66 blocks disagree: csplint/B006, csplint/B012, csplint/B016, csplint_square/B011, csplint_square/B015",
"metrics": {
"blocks_checked": 66,
"blocks_deferred": 14,
"blocks_matched": 59,
"blocks_matched": 61,
"blocks_waived": 0
},
"passed": false
Expand Down Expand Up @@ -1418,11 +1418,11 @@
"stopped_by": "static.rwset",
"verdicts": {
"static.rwset": {
"detail": "18/124 blocks disagree: splrep_msg/B002, splprep/B014, splprep/B016, splprep/B019, splprep/B020 (+13 more)",
"detail": "1/124 blocks disagree: splrep_msg/B002",
"metrics": {
"blocks_checked": 124,
"blocks_deferred": 8,
"blocks_matched": 106,
"blocks_matched": 123,
"blocks_waived": 0
},
"passed": false
Expand Down Expand Up @@ -1650,11 +1650,11 @@
"stopped_by": "static.rwset",
"verdicts": {
"static.rwset": {
"detail": "7/187 blocks disagree: genrand_encode/B002, genrand_encode/B003, genrand_decode/B002, genrand_load_state/B003, genrand_load_state/B004 (+2 more)",
"detail": "3/187 blocks disagree: genrand_encode/B002, genrand_encode/B003, genrand_decode/B002",
"metrics": {
"blocks_checked": 187,
"blocks_deferred": 3,
"blocks_matched": 180,
"blocks_matched": 184,
"blocks_waived": 0
},
"passed": false
Expand Down Expand Up @@ -1904,23 +1904,42 @@
"oracle/f2py-golden": "ok",
"store/fs-evidence": "ok",
"transform/translate.numpy": "ok",
"verifier/differential.bitexact": "ok",
"verifier/static.rwset": "ok",
"verifier/symbolic.notary": "ok"
"verifier/differential.bitexact": "failed",
"verifier/static.rwset": "ok"
},
"stopped_by": null,
"stopped_by": "differential.bitexact",
"verdicts": {
"differential.bitexact": {
"detail": "80 points across 1 subprogram(s), all bit-exact",
"detail": "3 subprogram(s) could not be compared: searchsorted_dp: candidate raised: NameError: name 'SMALL' is not defined; searchsorted_idp: candidate raised: NameError:",
"metrics": {
"bit_exact": 80,
"bit_exact": 90,
"integer_mismatch": 0,
"integer_points": 0,
"integer_points": 10,
"max_rel": 0.0,
"max_ulp": 0,
"nan_mismatch": 0,
"points": 80,
"points": 90,
"subprograms": {
"searchsorted_dp": {
"error": "candidate raised: NameError: name 'SMALL' is not defined"
},
"searchsorted_dpi": {
"error": "candidate raised: NameError: name 'SMALL' is not defined"
},
"searchsorted_i": {
"bit_exact": 10,
"integer_mismatch": 0,
"integer_points": 10,
"max_rel": 0.0,
"max_ulp": 0,
"nan_mismatch": 0,
"points": 10,
"redrawn": 0,
"reshaped": 0
},
"searchsorted_idp": {
"error": "candidate raised: NameError: name 'SMALL' is not defined"
},
"sort": {
"bit_exact": 80,
"integer_mismatch": 0,
Expand All @@ -1935,7 +1954,7 @@
},
"trials": 10
},
"passed": true
"passed": false
},
"static.rwset": {
"detail": "62 blocks match",
Expand All @@ -1946,13 +1965,6 @@
"blocks_waived": 0
},
"passed": true
},
"symbolic.notary": {
"detail": "no rewrites to notarize; the translation is print-order faithful",
"metrics": {
"rewrites": 0
},
"passed": true
}
}
},
Expand Down Expand Up @@ -2360,11 +2372,11 @@
"stopped_by": "static.rwset",
"verdicts": {
"static.rwset": {
"detail": "15/106 blocks disagree: slsqp/B019, slsqpb/B002, slsqpb/B003, slsqpb/B006, reset_bfgs_matrix/B002 (+10 more)",
"detail": "7/106 blocks disagree: slsqpb/B002, slsqpb/B003, slsqpb/B006, reset_bfgs_matrix/B002, lsq/B026 (+2 more)",
"metrics": {
"blocks_checked": 106,
"blocks_deferred": 8,
"blocks_matched": 91,
"blocks_matched": 99,
"blocks_waived": 0
},
"passed": false
Expand Down
34 changes: 20 additions & 14 deletions src/recast/fortran/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,9 @@ def substitute(expr: Expr, name: str, replacement: Expr) -> Expr:
For the one legal self-reference in an initializer, a kind inquiry on the
constant being declared (``tol = max( 1.e-10_r8, epsilon(tol) )``): the
reference carries the constant's kind and nothing else, and the fold
renders reals as 64-bit, so a 64-bit literal stands in for it.
renders reals as 64-bit, so a 64-bit literal stands in for it. Every
occurrence is replaced: a parameter cannot name itself anywhere else in
its own initializer, so there is no other occurrence to preserve.
"""
if expr.kind == "name" and expr.text == name:
return replacement
Expand Down Expand Up @@ -205,52 +207,56 @@ def render(
REAL_CALLS = frozenset({"real", "dble", "sqrt"} | KIND_INQUIRIES)


def typed(expr: Expr) -> str | None:
def typed(expr: Expr, env: dict[str, str | None] | None = None) -> str | None:
"""``"real"``, ``"int"``, or ``None`` when a bare name leaves it open.

Type inference the fold needs for exactly one decision: whether a ``/``
is Fortran's integer division. A real literal or a real-valued call
anywhere in an operand makes the quotient real; ``int(...)`` and integer
literals make it integer; a name is whatever its initializer was, which
this tree does not carry.
literals make it integer; a name is what ``env`` says its declaration
was -- CLUBB's ``ep = Rd / Rv`` over two real parameters is a real
quotient, and a fold that guessed integer made it zero.
"""
if expr.kind == "real":
return "real"
if expr.kind == "int":
return "int"
if expr.kind == "name":
return None
return (env or {}).get(expr.text)
if expr.kind == "call":
if expr.text == "int":
return "int"
if expr.text in REAL_CALLS:
return "real"
kinds = {typed(a) for a in expr.args}
kinds = {typed(a, env) for a in expr.args}
if "real" in kinds:
return "real"
if kinds == {"int"}:
return "int"
return None


def with_integer_division(expr: Expr, *, default_integer: bool | None = None) -> Expr:
def with_integer_division(
expr: Expr, *, default_integer: bool | None = None, env: dict[str, str | None] | None = None
) -> Expr:
"""The tree with every integer ``/`` spelled ``//``.

Fortran divides two integers to an integer: ``nrk = runge_kutta_type / 10``
is 4, not 4.1. A quotient whose operands are both known integers is
marked; one with a name in it falls back to ``default_integer``, which
the caller sets from the whole initializer -- an expression with no real
literal and no real-valued call in it is integer arithmetic throughout,
because a name in it is an integer parameter or it would have had one.
marked; one with a name in it is typed by ``env`` (the declared types
of the constants resolved so far) and otherwise falls back to
``default_integer``, which the caller sets from the whole initializer.
"""
if default_integer is None:
default_integer = typed(expr) != "real"
default_integer = typed(expr, env) != "real"
if not expr.args:
return expr
args = tuple(with_integer_division(a, default_integer=default_integer) for a in expr.args)
args = tuple(
with_integer_division(a, default_integer=default_integer, env=env) for a in expr.args
)
text = expr.text
if expr.kind == "binary" and expr.text == "/":
kinds = {typed(a) for a in args}
kinds = {typed(a, env) for a in args}
if kinds == {"int"} or ("real" not in kinds and default_integer):
text = "//"
return Expr(expr.kind, text, args)
Expand Down
Loading