> I think a comparison of Robin's and Giorgio's description of how Sfx
> and SAINT handles executiing the k-pass and i-passes of an aopcode
> guarded by a conditional says it all:
>
>
> [Robin writes]
>
> Sfx currently evaluates the sub-guard-rate expressions unconditionally.
This
> is fairly sensible, and leads to a decent conceptual model of how things
> work.
>
> [Giorgio writes]
>
> The k-rate expression, if it is broken into intermediate statements
> like in saint, generates a k-block inside the a-rate-guarded block.
>
(1) These are not incompatible. Sfx and Saint generate the same results for
k-rate *EXPRESSIONS*. Sfx also allows (as an extension) k-rate *STATEMENTS*.
If Giorgio's "k-rate expression" encompasses k-rate statements as well, then
Sfx and Saint are fully compatible.
The purpose of the extension is ensure that:
aResult = SawWave(kCps);
and
if (kUseSawWave) {
aResult = SawWave(kCps)
} else {
aResult = SinWave(kCps)
}
work sensibly and equivalenty, even in the presence of i-rate and k-rate
expressions in the SawWave UDO.
(2) I have repeatedly stated that I believe that Sfx's behaviour wrt/
sub-guard-rate statements in opcodes in the guarded code block is probably
non-conformant, and repeatedly stated that I intend to issue a warning or
error in this case if I can figure out what the correct behaviour is.
My reading is that there are three possibilities: Sfx's extended behaviour;
SFront's behaviour, and something inbetween. I am relatively indifferent to
(1) and (3), but strongly opposed to (2).
Here are the three options I see:
(1) Mixed-rate opcodes are legal. Sub-guard-rate expressions in
conditionally evaluated UDOs are executed unconditionally. (Sfx's
implementation). I like this implementation, obviously, but there are
admittedly complications (such as copy-out of k-rate parameters in a-rate
guarded code) which are rather scary. Semantics for this case in particular
would have to be carefully defined. Three obvious options: (i) Copy-out
occurs uncoditionally. Probably the easiest to implement, for me! <wicked
evil grin>. (ii) The results are unspecified; (iii) the copy-out is
suppressed (the most annoying to implement). Sub-guard-rate expression in
UDOs within the body of a while loop are executed once at i-rate against the
state memory associated with the instance of the UDO. At-guard-rate
expressions are evaluated N-times per k-cycle against the state-memory of
associated with the instance of the UDO. Above-guard-rate expressions are
already explicitly forbidden by the language (statements within opcodes
cannot be faster than the opcode rate; specialops are forbidden; opcode
rates may not exceed the rate of the guard expression).
(2) Mixed-rate opcodes are illegal. Errors should be given.
(3) Mixed-rate opcodes are legal. Sub-guard-rate expressions in
conditionally evaluated UDOs should generate errors.
Closely-related issues related to oparrays will be dealt with below.
Pros and cons:
(1) Pro: To me this seems the most conceptually consistent. UDOs work
consistently inside or outside if or while blocks. Library code development
remains efficient. Cons: possible additional ambiguities. Interpreters will
have to do code-rewriting to move opcodes' i-rate execution out of guarded
code blocks (if they don't already), to avoid having to completely walk
recursive opcode definitions in search of i-rate statements. Additional
issues raised: semantics for oparrays need to be defined carefully:
mixed-rate opcodes combined with oparrays should probably be forbidden.
sub-guard-rate copy-out semantics need to be defined.
(2) Pro: Probably less burden to compiler writers. Almost certainly
unambiguous. Cons: Severely restricts the abilities of authors.
(3) Pros: Less risk of ambiguities than (1); Cons: libary opcodes will break
if used in inappropriate contexts.
oparray issues
--------------
oparrays have many of the same problems that conditional code blocks do.
SAOLC avoids most of these ambiguites by using the following general sort of
code:
imp_opcode() {
if (FIRST_TIME) {
allocate opcode storage
allocate argument storage.
}
if (K_PASS) {
save a k-rate parameter.
}
if (A_PASS) {
use a k-rate parameter.
calcualte state variables.
}
}
I'm of the opinion that the conditional branches in a-rate code will prevent
SAOL from runing in realtime. Sfx runs into severe problems with oparrays
because the structure of core and UDOs looks generally like this:
class CX_anopcode_ {
public:
void IEval(COrchestra *pGlobals, float_t &iRateArgs, ...);
void KEval(COrchestra *pGlobals, float_t &kRateArgs, ...);
float_t AEval(COrchestar *pGlobals, float_t &aRateArgs, ...); // for
core opcodes.
float_t m_RetVal[n]; // for UDOs.
}
The current common usage of aopcoce fracdelay(ksig method, xsig p1, p2)/
oparray fracdelay[n] poses problems because Sfx doesn't have a way to
perform SAOLC's feretting away of the ksig method call when executiong
fracdelay[aIndex](kMethod, kp1, kp2);
Simply put, oparrays seem to impose, by neccessity, a lower-bound guard rate
based on the rate of the oparray index. Ideally, I'd like to see this
lower-bound guard also extended to the rate of formal paramters e.g.
aopcode SomeOp(ivar iParam) { ... }
oparray SomeOp[5];
ksig k;
k = 0; while (k < 5) {
SomeOp(iVal);
***ERROR* ^ Sub-index-rate argument.
}
since there is no entirely sensible way that I can see for that i-rate
argument to get passed to the k-rate state and methods of SomeOp.
Assuming we treat the rate of the index as establishing a lower-bound guard
rate for the opcode, we can still not execute mixed-rate statements in the
opcode, because it becomes difficult to establish what exactly the lower
rate is. Combination of if and while statements, mixed with differnt rates
of indices make it impossible to determine at compile time which members of
an oparray need to have i- and k-rate execution passes executed and when.
Consider a nightmare case:
if (iTest1) {
while (iPrime < 10000) {
SomeOpArray[NextPrimeNumber(iTest)](1, 2);
}
}
if (kTest2) {
while (kSquare < 10000) {
SomeOpArray[NextSquare(kSquare)](3,4);
}
}
Sfx has the following implementation (implemented in the spirit of What is
Not Explicitly Forbidden is Legal). It maintains flags to indicate whether
i- and k-rate arguments and execution have been passed to the opcode. If an
oparray member has not had a k-rate execution pass, then the k-cycle is run
on the opcode, even if we are currently running at a-rate.
class CX_SomeOparrayMember {
public:
CX_SomeOp opstate; // the actual oparray state data.
void Clear() {
m_bIRateExectued = false;
m_bKRateExecuted = false;
}
...
void AEval() {
if (!m_bKRateExecuted) {
if (!m_bIRateExecuted) {
opstate.IEval(m_SavedIRateArguments);
m_bIRateExecuted = true;
}
opstate.KEval(m_SavedKRateArguments);
m_bKRateExecuted = true;
}
opstate.AEval();
}
Do I like this? Heck no. I think it's awful. But it is a potential solution.
Other variants might include re-running the i-cycle and k-cycle *every* time
at i-rate:
void CX_SomeOpArrayMember::AEVal() {
opstate.KEval(m_kSavedArgs, ..);
opstate.IEval(m_SavedIArgs, ..);
opstate.AEVal();
}
So. The open issues I see are:
(1) How to handle sub-oparray-index-rate expresisions within an opcode?
(2) How to handle sub-oparray-index-rate arguments to an opcode? (i) on
copy-in; (ii) on copy-out.
Solutions that I can see are:
(1) UDOs used in oparrays may not be multi-rate.
(2) The oparray index imposes a lower-bound guard rate on the opcode. The
opcode may not contain statements that evaluate at less than the guard-rate.
(3) The oparray index imposes a lower-bound guard rate on the formal
parameters of the opcode *and* the statements within the opcode.
(4) The oparray index imposes a lower-bound guard rate on the formal
parameters of the opcode. Statements which have a lower rate than the
slowest formal parameter are executed uncoditionally at the start of the
opcode.
(e.g.)
// at IEval time.
for (int i =0; i < n; ++i) oparray[i].IEval();
// KEval time:
oparra[kValue].Keval(kArguments);
// AEval time:
opparry[kValue].AEval(aArguments);
(5) Multiple k-rate execution passes at a-rate.
Each arate invocation causes i- and k- rate execution to occur as well.
(6) First-use of sub-index-rate i-arguments. The first use of an oparray
member at its' opcode rate sets it's subarray-rate members.
My personal vote? Door number 3: oparray index imposes a lower-bound guard
on formal paramters *and* statements.
Sfx's impelemntation of UDO oparrays is ragged at best, and changes as my
mood changes.
> -- Ensuring the semantics work with regards to user-defined opcodes that
> call user-defined opcodes that call user-defined opcodes, all of which
> have statements at all three rates.
>
(i) The general rule is i-rate expression evaluation precedes k-rate
expressions which precede a-rate expressions.
(ii) After that, Statements execute in lexical statement order. (ie. walk
the opcode tree).
> -- For the "opcodes that call opcodes that call opcodes" case, are there
> any special considerations if some of those opcodes are in fact oparrays.
>
Agreed. Deserves consideration.
This archive was generated by hypermail 2b29 : Mon Jan 28 2002 - 12:03:55 EST