Compare commits

...

49 Commits

Author SHA1 Message Date
Mike Gerwitz b378cbdf6a
WIP 2024-08-13 01:03:04 -04:00
Mike Gerwitz bbe775d870 summary: Warn on missing packages during typesetting
This restores the previous behavior (from before a couple commits ago), but
adds a warning.

We have a GitLab pipeline internally where we break apart certain
directories into separate jobs to parallelize them.  There is a bug relating
to build and retaining artifacts for rate tables that only manifests itself
for Summary Pages, and that needs to be resolved before this is removed.

DEV-15153
2023-10-20 16:24:26 -04:00
Mike Gerwitz 2770ee95ed summary.xsl: Restrict symbol lookup to linker dependency list
This accomplishes a couple of major things:

  1. Removes the need for package-specific symtable maps, since we can just
     use the global linker one; and
  2. Stops typesetting things that are not linked.

I didn't realize how much of the latter was happening.  On one of our large
programs, the filesize dropped from 64MiB to 51MiB.  Consider also that,
because the menu generation _does_ only take into account the linked symbol
list, _these items were never even accessible_, at least through normal
means, so the user couldn't even see them.

Compilation time for that program dropped from ~2m20s to ~1m.

DEV-15153
2023-10-20 15:35:47 -04:00
Mike Gerwitz 0928896935 summary.xsl: Introduce maps for l:dep and preproc:symtable
This begins to chip away at the terrible performance of this system by using
maps where linear scans of the symbol table were otherwise taking place.

This does not yet touch `preproc:sym-deps`, which has the same problem.

Further, we may be able to get rid of symbol table maps per package by
utilizing `l:dep`; more to come.

This trims ~40s off of one of our larger programs (3m -> 2m20s), which is a
start, but there's still a ton of room for improvement.  I don't expect it
to be blazingly fast after all the optimization attempts---this has the same
problem as the XSLT-based linker did, which ended up being reimplemented in
Rust as TAMER---but it'll help to improve times in the meantime.

DEV-15153
2023-10-20 15:35:08 -04:00
Mike Gerwitz 297bf4a506 preproc/symtable: Linear-time type resolution
This was optimized previously, having introduced
`@need-resolve-dtype`.  But, while that solved the problem for some
packages, it was not enough for a package with 40,000 symbols.

This introduces a map for typedefs' symbols and eliminates ~15s for that
package.

DEV-15114
2023-10-19 13:00:40 -04:00
Mike Gerwitz 959ff06539 depgen (preproc:symtable): Use for-each-group for deduplication
And here's the optimization that I had wanted to perform all along, but it
took some time to confidently get to this point.

`preceding-sibling` was used since the XSLT1 days for deduplication, before
`for-each-group` existed.  It works just fine for small inputs, but the
problem is we're doing this many thousands of times in larger packages, and
that really adds up.  (In this case, shaving ~8s off of one of our large
packages with ~20k symbols in play.)

DEV-15114
2023-10-19 13:00:40 -04:00
Mike Gerwitz abc37ef0cc depgen: Clean up TeX symbol generation
See the `preproc:tex-gen` comment for more information.  This retains the
existing behavior, cleaning it up quite a bit.

This has no impact on performance; it's just continued refactoring to
prepare for optimization.

DEV-15114
2023-10-19 13:00:39 -04:00
Mike Gerwitz 659a0e71fb depgen (preproc:symtable): Simplify symbol iteration
This just continues to refactor to try to make sense of this code, which has
evolved into quite the mess over the years.  The two primary goals are
to (a) find possible optimizations and (b) make sure how this functions is
clear for when it's reimplemented in TAMER.

I'm doing this in small commits so that the steps are more obvious.

The specific list of attributes is what I found to be output in practice in
the `xmlo` files.

DEV-15114
2023-10-19 10:56:22 -04:00
Mike Gerwitz f415e05f31 depgen: Remove lax symbol concept
I had to dig through the old repository (prior to extracting into this one)
to see why this was introduced.  It seems that it was for the linker, and
TAMER's linker has no concept of lax symbols, so this is not used.

To make matters worse, the code I modified here could not have worked (in
depgen) because `$syms/@name` _will always have a value_.

Anyway, removing this has no effect on the compiled packages.

DEV-15114
2023-10-19 10:56:22 -04:00
Mike Gerwitz b3f92e0678 depgen: Combine preproc:symtable sym-ref generation cases
This does not impact performance, but it makes it less confusing.  What a
mess this whole thing is.  I'll have to incrementally refactor it until it
makes sense enough to optimize.

For this who don't know, from XSLT 1.0 days: "rtf" means "result tree
fragment", before sequences were a thing, and you had to treat generated
trees specially.  Yeah, old code.

DEV-15114
2023-10-19 10:56:22 -04:00
Mike Gerwitz e20076235e _table-row_: Performance fix: place table in const/text() instead of const/@values
This is an interesting one.

For some context: TAME uses `csvm` files to provide syntactic sugar for
large tables of values ("rate tables", as they're often called, since they
contain insurance rates and other data).  This gets desugared into a `csv`
which in turn is compiled via `csv2xml` into a package.  That package uses
the `_table-*_` templates to define a table, which is represented as a
matrix using `const/@values`.

Here's an example of a generated table in a package:

```
  <t:create-table name="foo">
        <t:table-rows data="
          1,2,3;
          4,5,6;" />
  </t:create-table>
```

Some of the tables are quite large, generating tens of MiB of data in
`@data`.  This in itself isn't a problem.  But when Saxon parses the `@data`
attribute, it normalizes the whitespace, as mandated by the XML spec, and
removes the newlines.  Therefore, when the template is expanded and the
`xmlo` file is produced, the template produced a `const/@values` with a huge
amount of data on one line.

Then, when another package imports that `xmlo` file via `<import
package="..." />`, which is done via `document()` in XSLT, Saxon takes a
long time to parse it.  60s on my machine for a ~20MiB line.

This problem does not exist for JS fragments; Saxon doesn't mind large text
nodes.  So that is the approach that is taken here.

The template system doesn't have a way to output text yet, so this takes an
approach that minimizes changes as much as possible:

  - `param-copy` will expand `with-param/@value` as a text node.
  - `const/@values="-"` will cause TAME to use the child text node as the
    value of `@values`.
  - `_table-rows_` is modified to use the above two features.

The reason for using `@values="-"` is so that other parts of the compiler do
not have to be modified to recognize the new text convention, which is
otherwise awkward because newlines are text nodes.  The `-` convention comes
from command line programs, which means "read from stdin", generally; this
is okay since `-` is never a valid matrix specification.

This must have been a problem for a very long time, but wasn't all that
noticeable until recent performance optimizations, since so many other things
around it were also slow.

DEV-15131
2023-10-18 11:43:48 -04:00
Mike Gerwitz b82294b1bd preproc/symtable (preproc:symtable-complete): Do not re-process imported symbols
It's embarrassing how much time this saved on builds.

This apparently has always been doing a linear scan on the entire symbol
table for _every single param in the symbol table_, including those that
were imported.  This is not only unnecessary, but has no effect on the end
result of the system.

This cut build times almost in half, due to the number of symbols in some of
our packages.  All for unnecessary work.

Like most things that have quadratic (or polynomial) time complexity, they
don't show up during initial development, and are hard to even profile for,
because their effects are so small.  Now that our system has grown
substantially, it had a massive effect.

DEV-15114
2023-10-16 13:45:25 -04:00
Mike Gerwitz c1770d39ff expand-sequence: Update tests for recent changes
This should have been part of a previous commit.  So few parts of the system
utilize tests that I didn't notice that this was failing until after the
commit, since I'm not used to running them...even though I wrote them.

This system is being replaced with TAMER, so these changes largely just get
the tests working properly again.

DEV-15095
2023-10-13 11:31:41 -04:00
Mike Gerwitz 0b04807cfd prperoc/template: Use symtable map instead of preproc:symtable XPaths
This is more of the same, utilizing the map I created previously.  The
results are pretty significant; this commit and the previous cuts ~3.5m of
build time (if done serially) off of our largest system.

My goal is to get non-parallelizable portions of our build down to the point
where they are no longer the bottleneck.  This just about does that.

DEV-15095
2023-10-12 16:18:27 -04:00
Mike Gerwitz 7ccf0a0cfa preproc/expand.xsl: Remove check for injected templates
This incurs the cost of a symtable lookup via XPath for a feature that has
not been used in a long time (I don't even recall it).

DEV-15095
2023-10-12 15:56:02 -04:00
Mike Gerwitz a8ef1b4fd1 Begin to use symtable-map for template/macro passes
I wanted to get this committed before I continue because it required changes
to the `expand-sequence` system---tunneling params cannot pass through
functions, so this accepts a context to pass back to the calling system via
the `eseq:expand-node` override.

Otherwise, the key change here is the elimination of a preproc:symtable
XPath within a `template/@match`, which was a huge performance problem with
the preceding commits.  This improves build times modestly, but there are
more changes that this sets up for, so I'll keep going.

DEV-15095
2023-10-12 15:48:35 -04:00
Mike Gerwitz 50e31f4616 current/compiler/js-calc.xsl: Replace all preproc:symtable XPaths with map
This uses the already-available symtable-map to avoid expensive XPaths
resulting in (what I assume to be) linear scans of the symbol table.

This effectively makes the fragment compilation time vanish.

This had the effect of shaving ~4.5m total off of our largest system (if I
were to do `-j1`), and a couple minutes when run in parallel.

DEV-15095
2023-10-12 12:00:45 -04:00
Mike Gerwitz b7372fe7cd current/compiler/js-calc.xsl (compile-calc-value): Drastically reduce matching complexity
This takes a bunch of individual templates and combines them into one, while
also utilizing the already-available symbol table map in place of using an
XPath on `preproc:symtable`.

The results are much more drastic than I was expecting.

I was exploring this because one of our largest packages was spending most
of its time (~5m) in fragment compilation, which was a surprise to
me.  Prior work I did for runtime optimizations led to optimizations in its
parent `js.xsl`, but not in `js-calc.xsl`, which has largely been untouched
since it was originally written for XSLT 1 over 10 years ago.

Because it was originally written for XLST 1, it does not take advantage of
maps, tunneling variables, or various other options.  Further, it was
written in a naive way that was convenient (and clear) at the time, and
wholly acceptable for smaller inputs.  But, as is the case with
quadratic-time systems, there are severe growing pains.

This change reduced the package compilation time from 5-6m down to 1m15s,
and this was just the first attempt at optimizing it.  I should have taken a
look at this long ago, but my efforts were focused on TAMER, and I didn't
want to divert that focus.  That was a mistake.  Symptoms of this problem
were already prevalent ~10 months ago, when the package was taking 3 minutes
to compile (so the time has since doubled).

This also eliminates `@magic`, which has not been used for a long time (it
used to be used for a "constant" that held the current date/time; such a
value is now passed into the system like any other input).

After making this change, the resulting packages are byte-for-byte
identical.

I also noticed, though I haven't tried to measure it, that there seem to be
fewer multi-core spikes; this is possibly related to Saxon not trying to
evaluate expensive `template/match` expressions concurrently anymore.  If
true, this will also help with resource contention for parallel builds.

DEV-15095
2023-10-12 11:32:22 -04:00
Mike Gerwitz b2a996c1df expand-sequence/expand-group: Retain until hoisting
This is a rather small change for quite a bit of effort in researching what
was going wrong.  It's at last seven rabbit holes deep, or maybe several
herd of yaks, depending on your choice of measure and the current conversion
rate.

The problem can be summarized fair succinctly:
`expand-sequence/expand-group` exists to prevent an expansion repass for
every single child element of the `expand-sequence`, which would be
quadratic.  Basically, it restores the usual template expansion process for
that set of children.

But apparently `expand-group` was stripped on the first pass, which expanded
its children inline, which then meant that each of the children were subject
to their own individual passes, defeating the purpose of the optimization.

As is the nature of quadratic-time processes, that was not noticed until
inputs became especially large, and not only that, but were combined with
nested `expand-sequence`s.

I would say that this never worked the way that I intended it to, but I'm
not certain.  I was working quite a bit with TeX at the time, so it's
possible that I modeled it after `\expandafter`.  But that's not an
appropriate model for TAME.

TAMER will be removing expand-sequence entirely, since it will have enough
of an understanding of the source system to determine what requires
expansion and what requires ordering (e.g. for symbol table
iteration).  I'll also be making changes to simplify the process by further
restricting what type of iteration can take place.  But for the time being,
the change was necessary.

In our largest systems, this change cut off ~15m total of build time if run
serially (at `-j1`).  After sorting two runtabs for comparison (e.g. `sort
-k4`), you can get the total like so:

   $ paste <( sort -k4 runtab-a ) <( sort -k4 runtab-b )
       | grep xmlo\$ \
       | cut -f2,5,6 \
       | awk '{ total += ($1 - $2) } END { print total / 1000 }'

Similarly, this Awk expression will give the time differences:

   $ awk '{ print ($1 - $2)/1000, $5 }'

Further, the previous commit also introduced a `xmle-sym-cmp` tool to check
for differences between xmle symbol tables in an automated way, irrespective
of ordering (since there are many valid topological sorts).  This revealed
that the change fixed a bug (likely because of the forced repass after
`expand-group` hoisting) that was causing symbol table introspection to fail
to discover symbols in certain cases, which in our case, was resulting in
the failure to generate a small number of aggregate classifications
correctly.  The whole repass system is a concerning mess, but it's not worth
the effort to try to redo all of that when that work can be done in TAMER.

DEV-15069
2023-10-10 16:16:39 -04:00
Mike Gerwitz 7692d0d848 tools/xmle-sym-cmp: New tool for comparing xmle symbols
See the description for more information.  This takes place of manual
diffing that I've been doing over the years, which is error-prone and
time-consuming.

Note that a successful diff doesn't mean "we're good!"; other things could
have failed.  This specifically checks xmle symbol sets, nothing more.

DEV-15069
2023-10-10 11:47:17 -04:00
Mike Gerwitz b0eca41c96 Remove legacy classification system flag
This should have been cleaned up long ago; it hasn't been used for a couple
of years now.

DEV-10806
2023-10-05 11:55:24 -04:00
Mike Gerwitz 5e883e3c4f expand-sequence: Invoke is-expandable only once per expansion head
`is-expandable` is an expensive XPath, and it was being invoked twice per
node: one for each complementary `match`.

DEV-10806
2023-10-05 10:42:59 -04:00
Mike Gerwitz 3d56fe289d tame: TAME_DEBUG_RANDOM_NOACK: New environment variable to test runner ACK failures
When set, this will cause runner ACK failures to occur approximately every
1/N requests.  This allows manually testing conditions that would normally
only occur under severe resource contention.

DEV-10806
2023-10-03 16:22:47 -04:00
Mike Gerwitz 3fad6c6375 tamed: Do not inject unexpected exit on explicit reload request
When the client requests a reload (e.g. on ACK wait failure), we should not
terminate, since we are expecting to attempt the request again.

This was broken by the previous commit.

DEV-10806
2023-10-03 15:39:08 -04:00
Mike Gerwitz fb5947d59e Prevent hanging tame client when tamed runner is killed mid-process
It's a tad embarrassing that this has been eluding me for quite some
time.  I happened to run into it while testing the previous commit, which in
turn only existed because I was trying to optimize runner performance.

We'd have situations where, following a runner reload (exit code 129 =
SIGHUP), the build would simply hang indefinitely.  Apparently, `tame`, in
`command-runner`, blocks on a `read` without a timeout, expecting that the
FIFO attached to stdin will close if the runner crashes.  Maybe that used to
be the case, but that is no longer true today.

Because of that, the FIFO stays open, and read continues to block, waiting
for `DONE`.

Now, `tamed`, when seeing that a runner has crashed (which could have been
due to a reload), will check to see if that runner is marked busy.  If so,
that means that the client `tame` did not see `DONE`, because it did not
clear the flag via `command-runner`'s `mark-available.`  To notify the
client that someone went wrong, `tamed` will inject a `DONE` into the output
FIFO, which will allow the client to fail properly.

`dslc` catches exceptions and should output `DONE` under normal operating
conditions.  However, since some of our systems require so much memory to
build, we may encounter the OOM killer.  In that case, the process has no
time to recover (it is killed with SIGKILL), and therefore cannot output
`DONE`.  I suspect this is what has been happening to cause occasional build
hangs.

One final thing to clean this up: since we're properly handling reloads now,
based on this commit and the immediately preceding one, we can suppress the
warning when the code is 129 (see comments).

DEV-10806
2023-10-03 14:14:47 -04:00
Mike Gerwitz 7c9d6837fe Improve runner reload stability
The `tame` client has the ability to request a runner reload by issuing
SIGHUP to the runner PID.  This will cause `tamed` to kill the runner and
respawn it.

There were situations where this process hung or did not operate as
expected; the process was not reliable.  This does a number of things to
make sure the process is in a good state before proceeding:

  1. The HUP trap is set a single time, rather than being reset each time
     the signal is received for the runner.
  2. A `reloading` flag is set by the `tame` client when reloading is
     requested, and the flag is cleared by `tamed` when spawning a new
     runner.  This allows the client to wait until the reload is complete,
     and bail out otherwise.  Without this, there is a race, where the
     client may proceed to issue additional requests before the original
     runner terminates.
  3. Kill the runner with SIGKILL (signal 9).  This gives no opportunity for
     the runner to ignore the signal, and gives us confidence that the
     runner is actually going to be killed.

This may have caused errors that look like this (where 129 is the HUP
reload, which is expected):

  warning: failed runner 0 ack; requesting reload
  warning: runner 0 exited with code 129; restarting
  error: runner 0 still unresponsive; giving up

The last line may also be omitted, with instead an empty `xmlo` being
generated.

DEV-10806
2023-10-03 14:14:47 -04:00
Mike Gerwitz b8a36ec984 dscl: Replace process with `java`
This uses `exec` so that the process is replaced, simplifying process
management (we don't have to worry that the script will die but leave the
child hanging).  I'm not sure why I didn't do this originally.

DEV-10806
2023-10-03 14:14:43 -04:00
Mike Gerwitz 3d8c4d1ed0 preproc/domain: Eliminate duplicate domain generation
I have long forgotten about this system.  It converts typedefs into a more
generic domain, but the way in which it does so causes duplicate domains,
for two reasons:

  - Both `preproc:mkdomain` and the caller (`preproc:expand`) recurse into
    unions and generate domains; and
  - Each `preproc:expand` pass generates domains.

So, for example, if there are two `preproc:expand` passes on a union, then
the outer typedef (union) will have domains generated twice (once for each
pass), and the inner typedefs will have domains generated four times (for
each expansion pass, and twice for each pass).

This resolves the issue before the next commit makes further changes to move
this into a generated header file.
2023-09-28 10:21:52 -04:00
Mike Gerwitz 4e7d202d2d Remove __rseed random value from XSLT-based compiler
This was used before __pkguniq to generate identifiers.  Back then, I seemed
to think determinism was a problem and that randomness was desirable for
helping to ensure uniqueness between packages.  That was a mistake; we
_want_ a deterministic system (which is far easier to debug and verify the
results of), we just want uniqueness.

DEV-14965
2023-09-20 12:38:41 -04:00
Mike Gerwitz 418bd34005 tame: Introduce __pkguniq and preproc:pkg-generate-id to replace generate-id
This modifies the XSLT-based compiler to generate ids that are expected to
be unique across packages.  No such guarantee exists today; `generate-id()`
relies on the position of the node within a tree, which could easily be the
same across multiple compiler invocations for separate packages.

This situation seldom occurs, but has happened with increased frequency
lately in a system with >1000 packages.  It is more likely to occur in
packages that are very similar to one-another or where the beginning of the
package is similar (such as packages used as configuration for taxes for
each individual state).

This derives a SHA-256 hash from the canonical package name (well, not
canonical acccording to TAMER, but close: without the leading slash),
truncating it to 32 bits.  I used a birthday attack to estimate what the
size of this value ought to be: sqrt(2^32) = 65536, which is way more
packages than the poor XSLT-based compiler is going to handle.

If ever it needs to be increased due to conflicts, that is simple enough.

DEV-14965
2023-09-20 12:33:34 -04:00
Mike Gerwitz bdd98a5d92 tamer: asg: Require that all template parameters be referenced in body
This ensures that each metavariable defined within a template (a template
parameter) has, by the time that the template definition is ended, at least
one reference to each metavariable.

This has practical benefits---ensuring that you haven't forgotten to use a
metavariable; ensuring that you clean up when code is removed; and ensuring
that you didn't accidentally delete some reference that you didn't intend to
(at least in the case of _all_ references...)---but the rationale for it in
this commit is a bit different.  More on that below.

This does introduce the negative effect of making it more difficult to
consume inputs without utilizing them, acting more like a relevant type
system (in terms of substructural type systems and with respect to
metavariables, at least).  You can for now reference them in contexts that
would reasonably have no effect on the program or be optimized away, but in
the future it'd be nice to explicitly state "hey this isn't intended to be
used yet", especially if you're creating shells of templates, or trying to
maintain BC in a particular situation.  But more on that in the future.

For now, the real reason for this change is because of how I intend for
template expansion to work: by walking the body.  Rather than having to
check both the parameters of the template and then expand the body
separately, we can simply trust that each parameter is referenced.  Then,
after rebinding metavariable references, we can perform the same check on
the expansion template to see if there were arguments provided that do not
correspond to parameters.

This also adds flexibility with parameters that are used
conditionally---we'll be able to have conditionally required parameters in
error reporting.

More information on this is coming, though; it'll be included in the docs of
the commit that introduces the changes.

DEV-13163
2023-09-19 15:58:33 -04:00
Mike Gerwitz 0b84772853 tamer: asg: Support abstract independent clauses (for desc)
The number of files that had to be changed here emphasizes the need for a
better abstraction for indirection in the future.  I just don't have the
time to do so yet.

This introduces the same type of indirection that was needed for abstract
bindings: we need to be able to distinguish between a metavariable
identifier's lexical value as a _string_ vs. a _reference_; the former is
how it was interpreted previously, which meant that interpolation would not
cause an edge to be created to the metavariable, which in turn means that
the metavariable was not referenced.  This in turn blocked a change to
require that all metavariables be referenced.

I'm climbing back up my stash stack.

DEV-13163
2023-09-19 15:58:32 -04:00
Mike Gerwitz 666b3d312f tamer: asg::air: Generalize doc parsing
This creates a new `AirDocAggregate` child parser and handled documentation
in a consistent way for all object types.  The logic that was previously
object-specific now lives on the edge hooks that did not exist when doc
parsing was originally introduced.

This prepares for doc refs so that metavariables can be used for doc
generation (most notably for interpolated documentation strings in
templates).

This also introduces a new error type specifically for meta, giving guidance
to use `<text>`.  I don't like that it builds in source language assumptions
in the help message provided, however I'd rather it be able to provide some
guidance to people, especially given that the XML source language is the
only one recognized by TAMER at the moment.

DEV-13163
2023-09-19 15:58:31 -04:00
Mike Gerwitz e46a6f65ce tamer: Makefile.am: check-cargo-partial: New target
For running individual test cases without having to break the abstraction of
the Makefile and run `cargo test` directory.

DEV-13163
2023-08-09 11:59:12 -04:00
Mike Gerwitz 6c2bfa936a tamer: asg::graph::Asg::try_map_obj: Make private
Only internal modules ought to be able to mutate objects.  This is important
now with the use of the `f::Map` trait, since traits don't allow for private
methods, which were previously the means of maintaining encapsulation.

DEV-13163
2023-08-04 13:22:55 -04:00
Mike Gerwitz 7001b50543 tamer: asg::air::expr: Infer concrete/abstract state from child expressions
See included documentation for more information.  This completes `MetaState`
inference, for now, until we are able to be notified when missing
identifiers acquire bindings.

DEV-13163
2023-08-04 13:20:44 -04:00
Mike Gerwitz ad9b6d1582 tamer: asg::air::expr: Track concreate/abstract status
This moves us closer toward template expansion by allowing us to look at an
`Expr` and determine whether it requires any expansion at all.  Naturally,
if something doesn't require expansion, we're able to duplicate it in the
tree without duplicating any data on the graph by simply adding a tree edge
to it.  But, that's for a future commit; this just tracks the status.

Just as with some recent previous changes, we're not yet notifying `Expr`s
when an `Ident` is no longer `Missing`, and so order matters for now.  This
will be rectified in the future.

DEV-13163
2023-08-04 13:20:44 -04:00
Mike Gerwitz 1ceedac234 tamer: span::dummy: Up to 30 dummy spans
DEV-13163
2023-08-04 00:20:20 -04:00
Mike Gerwitz 729871fe15 tamer: f: impl_mono_map: Support for non-tuple struct
DEV-13163
2023-08-02 10:20:55 -04:00
Mike Gerwitz 0ad7414b9e tamer: asg::graph::object::expr: Convert from tuple struct
This prepares for the addition of more fields.

I don't want to sacrifice exhaustiveness checking, which is unfortunately
more verbose for structs when ignoring fields.  This verbosity will be
adopted in certain situations, like shown in this patch, to ensure that
we're made aware of areas of the program that require our attention when
fields are added.

This also shows that I'll need a struct arm for `impl_mono_map!`.

DEV-13163
2023-08-02 10:16:13 -04:00
Mike Gerwitz d42c5584d0 tamer: asg::air::tpl: Refactor TplEndRef parsing
This removes the token of lookahead and just does what needs to be done in a
more clear manner.  There is no room for interpretation in what this is
doing now, and `TplEnd` delegates to `close` just as this does.

DEV-13163
2023-08-01 10:43:51 -04:00
Mike Gerwitz 9f74c0fc92 tamer: asg::graph: Strict static enforcement of tree/cross edge spans
We are now able to have confidence that the graph is properly constructed
with or without a reference span depending on whether the edge is a tree or
cross edge.  Consequently, the span is now a reliable indicator of whether
an edge is tree or cross in _all_ cases, not just in dynamic ones.

In fact, this did catch a couple cases where the spans were _not_ properly
applied.

This will in turn give me confidence moving forward with static analysis
based on the graph (and edge hooks).

I could go further than this by introducing a new span type in place of
`Option<Span>`, which would also allow me to get rid of having two methods
on `Asg`, but I want to move on for now; this can be cleaned up more later
on.

It's also worth noting that this explicit method-based distinction between
edge types also means that each caller will carefully consider how the
operation affects the graph.  Previously, that consideration was framed very
differently: "do I need a contextual span or not?".  That's not the right
question to ask.

DEV-13163
2023-08-01 09:41:35 -04:00
Mike Gerwitz c0ba827d90 tamer: asg::graph: {ctx=>rel}_span: Emphasize span use
When this span was originally introduced for an edge, I wasn't sure whether
it may have use cases beyond just references.  At this point, I'd rather be
explicit; it can evolve over time if need be.

There is nothing yet that prevents a span from being added to a tree edge,
though.

DEV-13163
2023-07-31 23:49:34 -04:00
Mike Gerwitz 62884b2e68 tamer: asg::graph::object::tpl: Refactor common application shape inference
This also hoists `?` out of the inner match arms to make control flow easier
to follow.  I try to balance convenience with brutal explicitness, to make
control flow more obvious and make it easier to reason about what the system
is doing at every point in the program.

DEV-13163
2023-07-31 15:13:14 -04:00
Mike Gerwitz 17589939dd tamer: asg::graph::object::ident::IdentDefinition: New sum type
This allows removing the `diagnostic_panic!` from the `Ident` edge handling
for `Tpl`.

I do need to create a generic sum type macro at some point, considering how
extensively these are used.

DEV-13163
2023-07-31 14:27:36 -04:00
Mike Gerwitz deede5ff21 tamer: asg::graph::object::tpl: Infer shape from referenced identifiers
Oh, identifiers.  What a mess you make of the graph.  As indirection tends
to do, I suppose.

This still needs some cleanup; I wanted to get something working first.  In
particular:

  - Common operations need refactoring;
  - Having all this code in the edge definition makes for a mess;
  - `Ident` references really ought not be returned by `Ident::definition`
    since an identifier will never be a definition.

...I'm sure there's more.

DEV-13163
2023-07-31 13:54:40 -04:00
Mike Gerwitz 087ef45153 tamer: asg::graph::object::ident::definition{=>_narrow} and new `definition`
This renames the previous operation to `definition_narrow` and creates a new
`definition` that does not attempt to narrow.  This is important for
distinguishing between a missing defintion and a definition of a given type,
and will be important now that we're beginning to reason about identifiers
that were resolved during parsing.

DEV-13163
2023-07-30 23:15:03 -04:00
Mike Gerwitz 1c06605188 tamer: asg::air::tpl::test::apply: Move template application tests
This may not be the final home, but they're about to get much larger in
number.

DEV-13163
2023-07-29 00:33:02 -04:00
Mike Gerwitz 5dd7b7f1e8 tamer: asg::graph: Revert removal of AsgRelMut::pre_add_edge commit callback
I changed my mind from the previous commit---I do in fact want to inhibit
edge additions in some cases, to override what is added (e.g. to wrap a
template application in an Expr workspace).

The approach that I was trying to take was to override `from_oi` and return
it to the caller, but with all the dynamic edges, that ended up being a lot
more code than I feel was worth it; it was too complex.  This is a much
simpler (and more flexible) approach---we simply carry out whatever graph
operations we want in `pre_add_edge` using the provided `asg` reference, and
then after we're done, simply omit `commit` on the original.

DEV-13163
2023-07-28 00:13:35 -04:00
67 changed files with 4123 additions and 1537 deletions

View File

@ -36,7 +36,7 @@ rater-path()
}
CLASSPATH="$CLASSPATH:@DSLC_CLASSPATH@:$dslc_jar" \
"@JAVA@" @JAVA_OPTS@ $JAVA_OPTS \
export CLASSPATH="$CLASSPATH:@DSLC_CLASSPATH@:$dslc_jar"
exec "@JAVA@" @JAVA_OPTS@ $JAVA_OPTS \
com.lovullo.dslc.DslCompiler \
"$( rater-path )"

View File

@ -22,13 +22,14 @@ set -euo pipefail
declare mypath; mypath=$( dirname "$( readlink -f "$0" )" )
readonly mypath
declare -ri EX_NOTAMED=1 # tried to start tamed or runner but failed
declare -ri EX_STALLED=2 # runner stalled and could not recover
declare -ri EX_NORUN=3 # no available runners
declare -ri EX_DLOCK=4 # failed to get a lock to start tamed
declare -ri EX_BLOCK=5 # failed to get a lock for busy runner check
declare -ri EX_NODONE=6 # tamed did not provide a DONE with exit code
declare -ri EX_USAGE=64 # incorrect usage; sysexits.h
declare -ri EX_NOTAMED=1 # tried to start tamed or runner but failed
declare -ri EX_STALLED=2 # runner stalled and could not recover
declare -ri EX_NORUN=3 # no available runners
declare -ri EX_DLOCK=4 # failed to get a lock to start tamed
declare -ri EX_BLOCK=5 # failed to get a lock for busy runner check
declare -ri EX_NODONE=6 # tamed did not provide a DONE with exit code
declare -ri EX_UNEXPECTED=7 # runner terminated unexpectedly (see `tamed`)
declare -ri EX_USAGE=64 # incorrect usage; sysexits.h
# maximum amount of time in seconds to wait for runner to ack
# before forcibly restarting it
@ -93,7 +94,7 @@ command-runner()
# if not, then it may have stalled for some reason
verify-runner-ack "$*" < "$base/1" || {
echo "warning: failed runner $id ack; requesting reload" >&2
kill -HUP "$pid"
reload-runner-and-wait "$base" "$id" "$pid"
# give some extra time in case the host is under high load
sleep "$TAME_CMD_WAITTIME"
@ -213,7 +214,7 @@ spawn-runner-and-wait()
# wait on the expected id
local -ri nextid=$(( maxid + 1 ))
wait-for-runner "$root" "$nextid"
wait-for-runner "$root/$nextid" "$nextid"
echo "$nextid"
}
@ -314,6 +315,33 @@ verify-runner()
}
# Request a runner reload and wait for the reload to complete.
#
# Before requesting the reload, a `reloading` flag will be set on the
# runner. This flag is expected to be cleared by tamed once the runner has
# been restarted in response to SIGHUP.
#
# See `wait-for-runner` for more information on waiting for the flag.
reload-runner-and-wait() {
local -r base="${1?Missing base}"
local -ri id="${2?Missing id}"
local -ri pid="${3?Missing pid}"
# mark the runner as unavailable
touch "$base/reloading"
# issue a reload request to the runner
kill -HUP "$pid"
# we must not continue before the runner has been reloaded, otherwise we
# may issue new requests to a process that is being killed
wait-for-runner "$base" "$id" || {
echo "error: failed to reload runner $id" >&2
exit "$EX_STALLED"
}
}
# Wait for command acknowledgment from runner
#
# The runner must respond within TAME_CMD_WAITTIME seconds
@ -323,6 +351,13 @@ verify-runner-ack()
{
local -r cmd="${1?Missing command}"
if [ -n "${TAME_DEBUG_RANDOM_NOACK:-}" ]; then
if [ $((RANDOM % ${TAME_DEBUG_RANDOM_NOACK:-1})) -eq 0 ]; then
echo "debug: TAME_DEBUG_RANDOM_NOACK triggered" >&2
return 2
fi
fi
read -t"$TAME_CMD_WAITTIME" -r ack || return
test "COMMAND $cmd" == "$ack" || {
# TODO check for ack mismatch once output race condition is fixed
@ -336,9 +371,12 @@ verify-runner-ack()
# Assumes that the runner is ready once the pidfile becomes
# available. Polls for a maximum of six seconds before giving up
# and exiting with a non-zero status.
#
# This will also wait for the clearing of the `reloading` flag set by
# `reload-runner-and-wait`, as part of that same time.
wait-for-runner()
{
local -r root=${1?Missing root}
local -r base=${1?Missing base}
local -r id=${2?Missing runner id}
# we could use inotify, but that is not installed by default
@ -346,7 +384,10 @@ wait-for-runner()
# another dependency (give up after 6 seconds)
local -i i=12
while test $((i--)); do
test ! -f "$root/$id/pid" || return 0
if [ -f "$base/pid" -a ! -f "$base/reloading" ]; then
return 0
fi
sleep 0.5
done
@ -416,7 +457,7 @@ _start-tamed()
# wait for tamed even if it was already started (just in
# case this script was executed right after tamed started
# but before it is done initializing)
wait-for-runner "$root" 0
wait-for-runner "$root/0" 0
}
@ -499,6 +540,11 @@ Environment Variables:
(see also --verbose)
TAME_CMD_WAITTIME number of seconds to wait for ack from
runner (default 3)
Debug Environment Variables:
TAME_DEBUG_RANDOM_NOACK randomly (1-in-N, where N is the value)
simulate a failed runner ACK to test
the runner restart system
EOF
exit $EX_USAGE

103
bin/tamed
View File

@ -129,16 +129,43 @@ spawn-runner()
# monitor runner usage and kill when inactive
stall-monitor "$base" &
# loop to restart runner in case of crash
while true; do
declare -i job=0
trap 'kill -INT $job' HUP
"$mypath/dslc" < "$base/0" &> "$base/1" & job=$!
declare -i status=0
wait -n 2>/dev/null || status=$?
echo "warning: runner $id exited with code $status; restarting" >&2
done &
# loop to restart runner in case of crash
(
declare -i job=0
trap force-job-reload HUP
force-job-reload() {
kill -9 $job
}
while true; do
# if this runner is busy, then it must have terminated while
# processing (otherwise the client `tame` would have marked it as
# available); let's act on its behalf so that the client sees that we
# failed (which we'll represent with error code 2).
declare -i busy=$(< "$base/busy")
if runner-is-busy "$base" && ! runner-is-reloading; then
inject-runner-unexpected-exit "$base" "$id"
fi
# store the time that the runner was started so that we can later
# determine if it should be restarted to forcefully reclaim memory
date +%s > "$base/created-ts"
"$mypath/dslc" < "$base/0" &> "$base/1" & job=$!
runner-done-reloading
declare -i status=0
wait "$job" 2>/dev/null || status=$?
# 129 = signal (128) + HUP (1), which is an explicit reload request
# that we need not report
if [ "$status" -ne 129 ]; then
echo "warning: runner $id exited with code $status (pid $job); restarting" >&2
fi
done
) &
echo "$!" > "$base/pid"
@ -149,6 +176,64 @@ spawn-runner()
}
# Whether the runner at the provided base is busy
runner-is-busy() {
local -r base="$root/$id"
declare -i busy=$(< "$base/busy")
test "$busy" -eq 1
}
# Whether the runner at the provided base is flagged as having a reload
# request
runner-is-reloading() {
local -r base="$root/$id"
test -f "$base/reloading"
}
# Clear the runner's `reloading` flag, if any.
#
# This flag is set by the `tame` client before requesting a
# reload. Clearing this flag allows the client to observe that reloading
# the runner is complete and requests may be issued.
runner-done-reloading() {
local -r base="$root/$id"
rm -f "$base/reloading"
}
# Inject an exit code into the runner's output stream indicating an
# unexpected exit
#
# The string `DONE n` is normally output at the end of a runner's
# compilation (via `dslc`), where `n` is the exit code. But if the runner
# terminates before compilation completes (e.g. is OOM-killed), then it will
# never have the chance to do so, leaving the client waiting for a
# response. If the client is not checking for stalls (due to
# configuration), it may hang indefinitely.
#
# This function will inject a message into the output stream of the runner
# as if `dslc` itself replied so that the `tame` client can observe a
# failure and react accordingly. This uses the `tame` `EX_UNEXPECTED` exit
# code.
#
# This also outputs a warning to stderr.
inject-runner-unexpected-exit() {
local -r base="${1?Missing base}"
local -ri id="${2?Missing id}"
echo "warning: runner $id exited unexpectedly" >&2
# TODO: Worth a shared file with `tame`?
local -ri EX_UNEXPECTED=7
echo "DONE $EX_UNEXPECTED" > "$base/1"
}
# Monitor the given runner runtab and append to the aggregate runtab
#
# The aggregate runtab is append-only and has a row-level lock to support

View File

@ -160,7 +160,11 @@
<const name="{@__tid@}_RATE_TABLE"
type="float"
desc="{@__tname@} table; {@__desc@}"
values="@data@" />
values="-">
<!-- `@values="-"` above tells TAME to read the value from the
child text node -->
<param-copy name="@data@" />
</const>
</if>
<unless name="@data@">
<const name="{@__tid@}_RATE_TABLE"

View File

@ -133,6 +133,8 @@
<template mode="js-name-ref" priority="5"
match="c:sum[@of]|c:product[@of]">
<param name="symtable-map" as="map(*)" tunnel="yes" />
<variable name="of" select="@of" />
<variable name="func" select="ancestor::lv:function" />
@ -142,10 +144,9 @@
<!-- is @of a function param? -->
<when test="
$func
and root(.)/preproc:symtable/preproc:sym[
@type='lparam'
and @name=concat( ':', $func/@name, ':', $of )
]
and $symtable-map( concat( ':', $func/@name, ':', $of ) )[
@type='lparam'
]
">
<value-of select="@of" />
@ -157,13 +158,7 @@
</when>
<!-- maybe a constant? -->
<when test="
root(.)/preproc:symtable/preproc:sym[
@type='const'
and @name=$of
]
">
<when test="$symtable-map( $of )[ @type = 'const' ]">
<text>C['</text>
<value-of select="@of" />
<text>']</text>
@ -477,6 +472,8 @@
<!-- TODO: this should really be decoupled -->
<!-- TODO: does not properly support matrices -->
<template match="c:value-of[ ancestor::lv:match ]" mode="compile-calc" priority="5">
<param name="symtable-map" as="map(*)" tunnel="yes" />
<param name="noindex" as="xs:boolean" tunnel="yes"
select="false()" />
@ -485,8 +482,7 @@
<choose>
<!-- scalar -->
<when test="
$noindex
or root(.)/preproc:symtable/preproc:sym[ @name=$name ]/@dim = '0'
$noindex or $symtable-map( $name )/@dim = '0'
">
<apply-templates select="." mode="compile-calc-value" />
</when>
@ -502,196 +498,126 @@
<!--
Generate JS representing the value of a function argument
Generate code for a `c:value-of`.
This will match whenever value-of is used on a name that matches any function
parameter.
XXX: We want to remain decoupled from lv if possible.
@return generated JS representing argument value or 0
This combines what was previously a number of individual templates into a
large `choose`; this is important to avoid duplicate symbol table lookups
and expensive XPaths as Saxon attempts to determine which template
matches.
-->
<template mode="compile-calc-value"
match="c:*[@name=ancestor::lv:function/lv:param/@name]">
match="*" priority="9">
<param name="symtable-map" as="map(*)" tunnel="yes" />
<!-- use the argument passed to the function -->
<apply-templates select="." mode="compile-calc-index">
<with-param name="value" select="@name" />
</apply-templates>
</template>
<variable name="value-sym" as="element( preproc:sym )?"
select="$symtable-map( @name )" />
<variable name="name" as="xs:string" select="@name" />
<!--
Using value from let expressions
-->
<template mode="compile-calc-value"
match="c:*[ @name=ancestor::c:let/c:values/c:value/@name ]">
<!-- compile the value with the index (if any) -->
<apply-templates select="." mode="compile-calc-index">
<with-param name="value" select="@name" />
</apply-templates>
</template>
<!--
Generate JS representing the value of a global constant
Since constants are intended only to prevent magic values during development
(and are not required at runtime), the value of the constant will be placed
directly into the compiled code. However, we will *not* do this if the
constant is a set, since its index may be determined at runtime.
Note that "magic" constants' values are not inlined.
@return quoted constant value
-->
<template mode="compile-calc-value"
match="
c:*[
@name=root(.)/preproc:symtable/preproc:sym[
@type='const'
and @dim='0'
]/@name
]
">
<variable name="name" select="@name" />
<variable name="sym"
select="root(.)/preproc:symtable/preproc:sym[ @name=$name ]" />
<!-- it is expected that validations prior to compiling will prevent JS
injection here -->
<choose>
<!-- "magic" constants should not have their values inlined -->
<when test="$sym/@magic='true'">
<text>C['</text>
<value-of select="@name" />
<text>']</text>
<!-- global scalar constant (@value is only defined when @dim is 0) -->
<when test="$value-sym[ @type = 'const' and @dim = '0' ]">
<variable name="value" as="xs:string"
select="$value-sym/@value" />
<!-- note: this used to check for @magic, which has long since been
removed -->
<sequence select="$value" />
</when>
<!-- non-scalar constants are large and are stored statically for
reference rather than being inlined -->
<when test="$value-sym[ @type = 'const' and not( @dim = '0' ) ]">
<variable name="value">
<text>C['</text>
<value-of select="$name" />
<text>']</text>
</variable>
<apply-templates select="." mode="compile-calc-index">
<with-param name="value" select="$value" />
</apply-templates>
</when>
<!-- TODO: is this worth a tunnel instead of `ancestor::`? -->
<!-- generator index -->
<when test="$name = ancestor::c:*[ @of ]/@index">
<!-- TODO: This shouldn't be true anymore; is this cast still needed? -->
<!-- depending on how the index is generated, it could be a string, so
we must cast it -->
<text>+</text>
<value-of select="$name" />
</when>
<!-- function argument -->
<when test="$name = ancestor::lv:function/lv:param/@name">
<!-- use the argument passed to the function -->
<apply-templates select="." mode="compile-calc-index">
<with-param name="value" select="$name" />
</apply-templates>
</when>
<!-- let expression value -->
<when test="$name = ancestor::c:let/c:values/c:value/@name">
<!-- compile the value with the index (if any) -->
<apply-templates select="." mode="compile-calc-index">
<with-param name="value" select="$name" />
</apply-templates>
</when>
<!-- @value should be defined when @dim=0 -->
<otherwise>
<value-of select="$sym/@value" />
<variable name="dim" as="xs:string"
select="$value-sym/@dim" />
<variable name="value">
<text>A['</text>
<value-of select="@name" />
<text>']</text>
</variable>
<!-- the awkward double-negatives are intentional, since @index may not
exist; here's what we're doing:
- If it's not a set, then indexes are irrelevant; always cast scalars
- Otherwise
- If an index was provided and it is not a matrix, cast
- Otherwise
- If two indexes were provided and it is a matrix, cast
-->
<!-- N.B. it is important to do this outside the value variable, otherwise the
cast may be done at the incorrect time -->
<if test="
(
$dim='0'
or (
(
( @index and not( @index = '' ) )
or ( ./c:index )
)
and not( $dim='2' )
)
or (
( $dim='2' )
and ./c:index[2]
)
)
and not(
parent::c:arg
and not( @index )
)
">
<text>+</text>
</if>
<apply-templates select="." mode="compile-calc-index">
<with-param name="value" select="$value" />
</apply-templates>
</otherwise>
</choose>
</template>
<!--
Generates JS representing the value of a constant as part of a set
Since the index of constant sets can be determined at runtime, we need to
store all possible values. As such, we shouldn't repeat ourselves by inlining
all possible values; instead, we'll reference a pre-generated set of values
for the particular constant.
@return generated code representing value of a variable, or 0 if undefined
-->
<template mode="compile-calc-value"
match="
c:*[
@name=root(.)/preproc:symtable/preproc:sym[
@type='const'
and not( @dim='0' )
]/@name
]
">
<variable name="value">
<text>C['</text>
<value-of select="@name" />
<text>']</text>
</variable>
<apply-templates select="." mode="compile-calc-index">
<with-param name="value" select="$value" />
</apply-templates>
</template>
<!--
Generate JS representing the value of a generated index
@return generated code associated with the value of the generated index
-->
<template mode="compile-calc-value"
match="c:*[ @name = ancestor::c:*[ @of ]/@index ]">
<!-- depending on how the index is generated, it could be a string, so we must
cast it -->
<text>+</text>
<value-of select="@name" />
</template>
<!--
Generates JS representing the value of a variable
If the variable is undefined, the value will be considered to be 0 (this is
especially important for the summation of sets within this implementation).
That is: a value will never be considered undefined.
@return generated code representing value of a variable, or 0 if undefined
-->
<template match="c:*" mode="compile-calc-value">
<variable name="name" select="@name" />
<variable name="pkg" as="element( lv:package )"
select="root(.)" />
<variable name="dim"
select="$pkg/preproc:symtable/preproc:sym[ @name=$name ]/@dim" />
<!-- retrieve the value, casting to a number (to avoid potentially nasty
string concatenation bugs instead of integer/floating point arithmetic)
as long as we're either not a set, or provide an index for the set -->
<variable name="value">
<text>A['</text>
<value-of select="@name" />
<text>']</text>
</variable>
<!-- the awkward double-negatives are intentional, since @index may not
exist; here's what we're doing:
- If it's not a set, then indexes are irrelevant; always cast scalars
- Otherwise
- If an index was provided and it is not a matrix, cast
- Otherwise
- If two indexes were provided and it is a matrix, cast
-->
<!-- N.B. it is important to do this outside the value variable, otherwise the
cast may be done at the incorrect time -->
<if test="
(
$dim='0'
or (
(
( @index and not( @index = '' ) )
or ( ./c:index )
)
and not( $dim='2' )
)
or (
( $dim='2' )
and ./c:index[2]
)
)
and not(
parent::c:arg
and not( @index )
)
">
<text>+</text>
</if>
<apply-templates select="." mode="compile-calc-index">
<with-param name="value" select="$value" />
</apply-templates>
</template>
<!--
Include the index if one was provided
@ -712,11 +638,16 @@
@return index (including brackets), if one was provided
-->
<template match="c:*" mode="compile-calc-index">
<param name="symtable-map" as="map(*)" tunnel="yes" />
<param name="value" />
<variable name="index" select="@index" />
<choose>
<when test="@index">
<when test="$index">
<variable name="index-sym" as="element( preproc:sym )?"
select="$symtable-map( $index )" />
<text>(</text>
<value-of select="$value" />
@ -736,13 +667,11 @@
</when>
<!-- scalar constant -->
<when test="@index = root(.)/preproc:symtable/preproc:sym
[ @type='const'
and @dim='0' ]
/@name">
<value-of select="root(.)/preproc:symtable
/preproc:sym[ @name=$index ]
/@value" />
<when test="$index-sym[ @type='const' and @dim='0' ]">
<variable name="value" as="xs:string"
select="$index-sym/@value" />
<value-of select="$value" />
</when>
<!-- otherwise, it's a variable -->
@ -849,8 +778,12 @@
@return generated function application
-->
<template match="c:apply" mode="compile-calc" priority="5">
<param name="symtable-map" as="map(*)" tunnel="yes" />
<variable name="name" select="@name" />
<variable name="self" select="." />
<variable name="func-sym" as="element( preproc:sym )"
select="$symtable-map( $name )[ @type = 'func' ]" />
<call-template name="calc-compiler:gen-func-name">
<with-param name="name" select="@name" />
@ -862,13 +795,7 @@
<!-- generate argument list in the order that the arguments are expected (they
can be entered in the XML in any order) -->
<for-each select="
root(.)/preproc:symtable/preproc:sym[
@type='func'
and @name=$name
]/preproc:sym-ref
">
<for-each select="$func-sym/preproc:sym-ref">
<text>, </text>
<variable name="pname" select="substring-after( @name, $arg-prefix )" />
@ -947,8 +874,12 @@
-->
<template mode="compile-calc" priority="7"
match="c:apply[ compiler:apply-uses-tco( . ) ]">
<param name="symtable-map" as="map(*)" tunnel="yes" />
<variable name="name" select="@name" />
<variable name="self" select="." />
<variable name="func-sym" as="element( preproc:sym )"
select="$symtable-map( $name )[ @type = 'func' ]" />
<message select="concat('warning: ', $name, ' recursing with experimental guided TCO')" />
@ -958,13 +889,7 @@
<!-- reassign function arguments -->
<variable name="args" as="element(c:arg)*">
<for-each select="
root(.)/preproc:symtable/preproc:sym[
@type='func'
and @name=$name
]/preproc:sym-ref
">
<for-each select="$func-sym/preproc:sym-ref">
<variable name="pname" select="substring-after( @name, $arg-prefix )" />
<variable name="arg" select="$self/c:arg[@name=$pname]" />

View File

@ -41,8 +41,6 @@
<!-- legacy classification system -->
<include href="js-legacy.xsl" />
<!-- and whether to enable it (set to non-empty string) -->
<param name="legacy-classify" select="''" />
<!-- calculation compiler -->
<include href="js-calc.xsl" />
@ -474,7 +472,7 @@
<param name="const" as="element( lv:const )" />
<variable name="values-def" as="xs:string?"
select="$const/@values" />
select="compiler:const-values( $const )" />
<choose>
<when test="$values-def and contains( $values-def, ';' )">
@ -489,6 +487,21 @@
</function>
<function name="compiler:const-values" as="xs:string?">
<param name="const" as="element( lv:const )" />
<!-- @values="-", a convention from command-line programs where '-' means
"read from stdin", will take the value from the child text of the
constant; this is done because Saxon performs very, very poorly on
huge single-line attributes (e.g. 60s for ~20MiB single-line
attribute) -->
<sequence select="if ( $const/@values = '-' ) then
$const/text()
else
$const/@values" />
</function>
<!--
Produce a sequence of items
@ -507,7 +520,9 @@
<when test="$set/@values and $allow-values">
<sequence select="tokenize(
normalize-space( $set/@values ), ',' )" />
normalize-space(
compiler:const-values( $set ) ),
',' )" />
</when>
<otherwise>
@ -641,20 +656,6 @@
</function>
<template mode="compile" priority="6"
match="lv:classify[ compiler:use-legacy-classify() ]">
<param name="symtable-map" as="map(*)" tunnel="yes" />
<sequence select="concat(
$compiler:nl,
'/*!lc*/',
string-join(
compiler:compile-classify-legacy( $symtable-map, . ),
'' ),
'/*lc!*/' )" />
</template>
<template match="lv:classify" mode="compile" priority="5">
<param name="symtable-map" as="map(*)" tunnel="yes" />
@ -664,8 +665,7 @@
<template mode="compile" priority="8"
match="lv:classify[
@preproc:inline='true'
and not( compiler:use-legacy-classify() ) ]">
@preproc:inline='true' ]">
<!-- emit nothing; it'll be inlined at the match site -->
</template>
@ -708,14 +708,6 @@
</function>
<function name="compiler:use-legacy-classify" as="xs:boolean">
<variable name="flagname" as="xs:string"
select="'___feature-newclassify'" />
<sequence select="$legacy-classify != ''" />
</function>
<function name="compiler:compile-classify-assign" as="xs:string">
<param name="symtable-map" as="map(*)" />
<param name="classify" as="element( lv:classify )" />
@ -2476,39 +2468,19 @@
]]>
</text>
<choose>
<when test="compiler:use-legacy-classify()">
<text>
function div(x, y)
{
return x / y;
}
<text>
function div(x, y)
{
if (y === 0) return 0;
return x / y;
}
function pow(x, p)
{
return Math.pow(x, p);
}
</text>
</when>
<!-- x/0=0 introduced with new classification system; see commit message
for more detailed information -->
<otherwise>
<text>
function div(x, y)
{
if (y === 0) return 0;
return x / y;
}
function pow(x, p)
{
if (x === 0) return 0;
return Math.pow(x, p);
}
</text>
</otherwise>
</choose>
function pow(x, p)
{
if (x === 0) return 0;
return Math.pow(x, p);
}
</text>
<sequence select="unparsed-text(
concat( $__path-root, '/src/js/sha256.js' ) )" />

View File

@ -319,7 +319,7 @@
<template match="lvm:pass[ root(.)/@lvmc:type = 'retmap' ]"
mode="preproc:depgen" priority="6">
<preproc:sym-dep name=":retmap:{@name}">
<preproc:sym-ref name="{@name}" lax="true" />
<preproc:sym-ref name="{@name}" />
</preproc:sym-dep>
</template>
@ -378,7 +378,7 @@
and root(.)/@lvmc:type = 'retmap' ]"
mode="preproc:depgen" priority="6">
<preproc:sym-dep name=":retmap:{@to}">
<preproc:sym-ref name="{@from}" lax="true" />
<preproc:sym-ref name="{@from}" />
</preproc:sym-dep>
</template>
@ -596,7 +596,7 @@
mode="preproc:depgen" priority="6">
<preproc:sym-dep name=":retmap:{@to}">
<for-each select=".//lvm:from">
<preproc:sym-ref name="{@name}" lax="true" />
<preproc:sym-ref name="{@name}" />
</for-each>
</preproc:sym-dep>
</template>

View File

@ -270,29 +270,15 @@
<template match="lv:match[@pattern]" mode="lvv:validate-match" priority="9">
<choose>
<!-- warn of upcoming removal -->
<when test="compiler:use-legacy-classify()">
<message select="concat( 'warning: ',
ancestor::lv:classify/@as,
': lv:match[@pattern] support is deprecated ',
'and is removed with the new classification ',
'system; use lookup tables instead' )" />
</when>
<!-- @pattern support removed in the new classification system -->
<otherwise>
<call-template name="lvv:error">
<with-param name="desc" select="'lv:match[@pattern] support removed'" />
<with-param name="refnode" select="." />
<with-param name="content">
<text>use lookup tables in place of @pattern in `</text>
<value-of select="parent::lv:classify/@as" />
<text>'</text>
</with-param>
</call-template>
</otherwise>
</choose>
<call-template name="lvv:error">
<with-param name="desc" select="'lv:match[@pattern] support removed'" />
<with-param name="refnode" select="." />
<with-param name="content">
<text>use lookup tables in place of @pattern in `</text>
<value-of select="parent::lv:classify/@as" />
<text>'</text>
</with-param>
</call-template>
</template>

View File

@ -39,15 +39,14 @@
exclude-result-prefixes="ext util xs">
<variable name="tex-defaults">
<preproc:syms>
<preproc:sym value="\alpha" vec="A" />
<preproc:sym value="\beta" vec="B" />
<preproc:sym value="\gamma" vec="\Gamma" />
<preproc:sym value="x" vec="X" />
<preproc:sym value="y" vec="Y" />
<preproc:sym value="z" vec="Z" />
</preproc:syms>
<!-- see preproc:tex-gen#3 -->
<variable name="tex-defaults" as="element( preproc:tex )+">
<preproc:tex value="\alpha" vec="A" />
<preproc:tex value="\beta" vec="B" />
<preproc:tex value="\gamma" vec="\Gamma" />
<preproc:tex value="x" vec="X" />
<preproc:tex value="y" vec="Y" />
<preproc:tex value="z" vec="Z" />
</variable>
@ -108,48 +107,37 @@
<for-each select="$deps, $deps//preproc:sym-dep">
<variable name="sym-name" as="xs:string"
select="@name" />
<variable name="cursym" as="element( preproc:sym )?"
select="$symtable-map( $sym-name )" />
<if test="not( $cursym )">
<message select="." />
<message terminate="yes"
select="concat( 'internal error: ',
'cannot find symbol in symbol table: ',
'`', $sym-name, '''' )" />
</if>
<!-- do not output duplicates (we used to not output references
to ourselves, but we are now retaining them, since those
data are useful) -->
<variable name="uniq" select="
preproc:sym-ref[
not( @name=preceding-sibling::preproc:sym-ref/@name )
]
" />
<!-- symbols must not have themselves as their own dependency -->
<if test="$uniq[ not( $cursym/@allow-circular = 'true' )
and ( @name = $cursym/@name
or @parent = $cursym/@name ) ]">
<message terminate="yes"
<preproc:sym-dep name="{@name}">
<!-- @tex provided an non-empty, or function -->
<for-each-group select="preproc:sym-ref"
group-by="@name">
<!-- symbols must not have themselves as their own dependency -->
<if test="not( $cursym/@allow-circular = 'true' )
and ( @name = $cursym/@name
or @parent = $cursym/@name )">
<message terminate="yes"
select="concat( '[preproc] !!! fatal: symbol ',
$cursym/@name,
' references itself ',
'(circular dependency)' )" />
</if>
</if>
<!-- grab the original source symbol for these references and augment them
with any additional dependency metadata -->
<variable name="syms-rtf">
<for-each select="$uniq">
<variable name="name" select="@name" />
<variable name="sym" as="element( preproc:sym )?"
select="$symtable-map( $name )" />
<!-- we should never have this problem. -->
<if test="not( $sym ) and not( @lax='true' )">
<if test="not( $sym )">
<message terminate="yes">
<text>[depgen] internal error: </text>
<text>could not locate dependency symbol `</text>
@ -159,140 +147,84 @@
</message>
</if>
<!-- copy and augment (we set @name because $sym/@name may not exist
if @lax) -->
<preproc:sym name="{@name}">
<if test="$sym">
<sequence select="$sym/@*" />
</if>
<preproc:meta>
<!-- retain type -->
<sequence select="$sym/@type" />
<sequence select="$sym/@dim" />
<!-- copy any additional metadata -->
<sequence select="@*[ not( local-name() = 'name' ) ]" />
</preproc:meta>
</preproc:sym>
</for-each>
</variable>
<variable name="syms" select="$syms-rtf/preproc:sym" />
<!-- only applicable if the symbol is @lax and the symbol was not
found in the local symbol table -->
<variable name="lax" select="
$uniq[
@lax='true'
and not( @name=$syms/@name )
]
" />
<preproc:sym-dep name="{@name}">
<!-- process symbols that have not been found in the local symbol
table (only applicable when cursym is @lax) -->
<for-each select="$lax">
<!-- the @lax flag here is simply to denote that this symbol may not
actually exist and that ignoring the check was explicitly
requested (and not a bug in the depgen process) -->
<preproc:sym-ref name="{@name}" lax="true">
<sequence select="preproc:meta/@*" />
</preproc:sym-ref>
</for-each>
<!-- @tex provided an non-empty, or function -->
<for-each select="
$syms[
( @tex and not( @tex='' ) )
or @type='func'
]">
<choose>
<!-- even if function, @tex overrides symbol -->
<when test="@tex and not( @tex='' )">
<preproc:sym-ref tex="{@tex}">
<sequence select="@*" />
<sequence select="preproc:meta/@*" />
</preproc:sym-ref>
</when>
<!-- must be a function; use its name -->
<otherwise>
<preproc:sym-ref>
<sequence select="@*" />
<sequence select="preproc:meta/@*" />
<attribute name="tex">
<text>\textrm{</text>
<value-of select="@name" />
<text>}</text>
</attribute>
</preproc:sym-ref>
</otherwise>
</choose>
</for-each>
<!-- no @tex, @tex empty, no function -->
<for-each select="
$syms[
( not( @tex ) or @tex='' )
and not( @type='func' )
]">
<variable name="name" select="@name" />
<variable name="sym" select="." />
<preproc:sym-ref>
<!-- minimal attribute copy (avoid data duplication as much as
possible to reduce modification headaches later on) -->
<sequence select="@name, @parent" />
<sequence select="preproc:meta/@*" />
<sequence select="$sym/@name,
$sym/@parent,
$sym/@type,
$sym/@dim,
$sym/@tex" />
<!-- assign a symbol -->
<variable name="pos" select="position()" />
<attribute name="tex">
<variable name="texsym" select="
$tex-defaults/preproc:syms/preproc:sym[
position() = $pos
]
" />
<!-- copy any additional metadata -->
<sequence select="@*[ not( local-name() = 'name' ) ]" />
<choose>
<when test="$sym/@tex and not( $sym/@tex='' )">
<value-of select="$sym/@tex" />
</when>
<variable name="tex" as="xs:string?"
select="if ( @tex ) then @tex else $sym/@tex" />
<!-- scalar/vector default -->
<when test="$texsym and number( $sym/@dim ) lt 2">
<value-of select="$texsym/@value" />
</when>
<!-- matrix default -->
<when test="$texsym">
<value-of select="$texsym/@vec" />
</when>
<!-- no default available; generate one -->
<otherwise>
<value-of select="
if ( number( $sym/@dim ) lt 2 ) then '\theta'
else '\Theta'
" />
<text>_{</text>
<value-of select="$pos" />
<text>}</text>
</otherwise>
</choose>
</attribute>
<attribute name="tex" select="
if ( $tex and not( $tex = '' ) ) then
$tex
else
preproc:tex-gen( @name,
@type,
number( $sym/@dim ),
position() )" />
</preproc:sym-ref>
</for-each>
</for-each-group>
</preproc:sym-dep>
</for-each>
</preproc:sym-deps>
</template>
<!--
Generate a default TeX symbol given a symbol's relative position within
some context.
Generally, the relative position is with respect to a parent
identifier. The TeX symbols are typically used in a let list to visually
define variables that are later used in rendered expressions (on e.g. the
Summary Page).
-->
<function name="preproc:tex-gen" as="xs:string">
<param name="name" as="xs:string" />
<param name="type" as="xs:string" />
<param name="dim" as="xs:double" />
<param name="relpos" as="xs:integer" />
<!-- TODO: TAMER'll probably address this before it's fixed here, but this
may fail to generate a symbol! -->
<variable name="texsym" as="element( preproc:tex )?"
select="$tex-defaults[ $relpos ]" />
<choose>
<when test="$type = 'func'">
<sequence select="concat( '\textrm{', $name, '}' )" />
</when>
<!-- scalar/vector default -->
<when test="$texsym and $dim lt 2">
<sequence select="string( $texsym/@value )" />
</when>
<!-- matrix default -->
<when test="$texsym">
<sequence select="string( $texsym/@vec )" />
</when>
<otherwise>
<variable name="theta" as="xs:string"
select="if ( $dim lt 2 ) then
'\theta'
else
'\Theta'" />
<sequence select="concat( $theta, '_{', $relpos, '}' )" />
</otherwise>
</choose>
</function>
<template mode="preproc:depgen" priority="7"
match="lv:rate">
<preproc:sym-dep name="{@yields}">

View File

@ -26,7 +26,9 @@
template that is intended for use with dslc should include this.
-->
<stylesheet version="2.0"
xmlns="http://www.w3.org/1999/XSL/Transform">
xmlns="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:preproc="http://www.lovullo.com/rater/preproc">
<!--
Absolute path to root of TAME
@ -71,14 +73,25 @@
-->
<param name="__relroot" />
<!--
Random value that may be used to seed random values
A package-unique string
XSLT is deterministic and does not offer support for generating random values;
its generate-id() function is not sufficient for cross-package generation.
You should use `preproc:pkg-generate-id` instead of this value directly.
This value is deterministic, derived from `__srcpkg`, and so will not
change between runs; it can be used to generate identifier names that are
unique across packages, which is not something that we can rely on
`generate-id()` for on its own.
In practice, this can be concatenated with other generated strings,
including `generate-id()`-derived strings.
_There is no guarantee that this string will begin with a letter_, so you
should generate your identifiers accordingly.
See `DslCompiler.java` for implementation.
-->
<param name="__rseed" />
<param name="__pkguniq" as="xs:string" />
<!--
@ -114,4 +127,12 @@
</choose>
</template>
<function name="preproc:pkg-generate-id" as="xs:string">
<param name="refnode" as="node()" />
<sequence select="concat(
'_pu', $__pkguniq, '_',
generate-id( $refnode ) )" />
</function>
</stylesheet>