Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
100 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
b811729
jax: a static test is spelled with Python logic, decided on its leaves
chenyueqi Sep 5, 2026
3573059
jax: a while loop's exit flag starts at the function top; its carries…
chenyueqi Sep 5, 2026
32f8b82
jax: a break flag under a branch is bound at the function top and car…
chenyueqi Sep 5, 2026
dbaad67
jax: the anchor's Python max/min go through the runtime
chenyueqi Sep 5, 2026
0ba0bb6
jax: a scalar math.erf/erfc goes through the runtime shim
chenyueqi Sep 5, 2026
272ff93
jax: a count under configuration switches is a trace-time value
chenyueqi Sep 5, 2026
d3484a5
jax: concreteness is judged as the exits leave the body; the early-re…
chenyueqi Sep 5, 2026
8873227
jax: a character local is never a carry; a static-expression store ta…
chenyueqi Sep 5, 2026
52cd7e4
jax: the return guard is the rest of the function, for concreteness a…
chenyueqi Sep 5, 2026
c4e4f77
jax: a stand-in call of trace-time values is static to the concretene…
chenyueqi Sep 5, 2026
ccd919c
jax: the concreteness scan's stand-in rule matches the backend's stat…
chenyueqi Sep 5, 2026
d9efe85
jax: a module constant through its alias is a trace-time value to the…
chenyueqi Sep 5, 2026
a139b53
jax: a concrete local's store is coerced through a tracer-tolerant shim
chenyueqi Sep 5, 2026
b64f765
jax: a string-only branch is trivial; the host fallback needs the anc…
chenyueqi Sep 5, 2026
5ac485f
jax: a static branch over string stores and dropped calls is trivial too
chenyueqi Sep 5, 2026
2e1fa6f
jax: the host fallback is unconditional again
chenyueqi Sep 5, 2026
7982885
jax: an integer conversion of a static expression is a static expression
chenyueqi Sep 5, 2026
3bdc5ae
jax: a for-else without a break is the completion value after the loop
chenyueqi Sep 5, 2026
80a6a15
jax: a character local is one by its guard init, whatever its stores …
chenyueqi Sep 5, 2026
0a2ec6f
jax runtime: the NumPy runtime's writers, constant fold and section s…
chenyueqi Sep 5, 2026
d716148
jax: a loop whose body is only a character local's stores is dropped
chenyueqi Sep 5, 2026
c4e8c82
jax: a flat kernel takes optional IN dummies; an unpassed OUT array i…
chenyueqi Sep 5, 2026
5876e1e
jax: the result buffer rebound by a plain kernel keeps its copies
chenyueqi Sep 5, 2026
a282641
jax: a flat kernel takes optional INOUT and OUT dummies too, and retu…
chenyueqi Sep 5, 2026
74e3e59
jax backend: the discard name is never a carry
chenyueqi Sep 5, 2026
14ab06a
corpus baseline: re-recorded at 74e3e59 (one more block matched, 897 …
chenyueqi Sep 5, 2026
134aa0e
jax: a local derived object is its components
chenyueqi Sep 6, 2026
b8dd6e9
verify: the tolerance gate measures conditioning before failing a dom…
chenyueqi Sep 6, 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
72 changes: 42 additions & 30 deletions corpus/baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -1170,12 +1170,12 @@
},
"numfor": {
"bare_files": 85,
"blocks": 896,
"deferred": 57,
"blocks": 897,
"deferred": 56,
"refusals": [
[
"generic 'X': no match",
12
11
],
[
"call to external subroutine 'X'",
Expand Down Expand Up @@ -1354,7 +1354,7 @@
}
},
"fortran:csplines": {
"deferred": 14,
"deferred": 13,
"imports": true,
"mechanical": false,
"parses": true,
Expand All @@ -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/67 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_checked": 67,
"blocks_deferred": 13,
"blocks_matched": 62,
"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