-
Notifications
You must be signed in to change notification settings - Fork 0
Macro Language Reference
- Overview
- Macro Definitions
- Macro Invocation (Evaluation)
- Predefined Macros
- SETENV/IFENV Conditional Processing
- INCLUDE Statement
- Limitations
FXCoreMP reads an FXCore assembler source file (usually named "*.fxc") and writes an output file which is
a modified version of the input. Modifications are made by the processing of macro statements in the source
file. The FXCoreMP processor is a textual substitution system, it does not generally evaluate mathematical expressions or
perform higher level functions (although the _eval() predefined macro can be used to explicitly perform mathematical
and string expression evaluation). Although the macro nesting and arguments can be elaborate, ultimately it results
in text substitution. There is also conditional processing capabilities (see $setenv and $ifenv statements).
FXCoreMP treats macro names, argument names, and ifenv-conditions, as case-insensitive.
The FXCoreMP macro language supports the following statements:
| Description | Macro Syntax |
|---|---|
| Macro definition |
$macro <name> <value>$macro <name>(argname1,argname2,...)$endmacro
|
| Macro invocation |
$<name>(val1,val2,...)$<name>(arg1=val1,arg2=val2,...)
|
| Expression evaluation | $_eval(expression) |
| Embed a file | $include <filename> |
| Set env value | $setenv <envparm>=<value> |
| Conditional processing |
$ifenv <envparm>=<value>$ifenv <envparm>!=<value>$endenv
|
Each of these are described in the following sections.
A macro definition specifies the name of the macro, the number and names of any arguments, and 1 or more lines of macro text.
A macro starts with $macro, followed by a name, optional arguments, and macro (substitution) text.
When processing an input file the processor reads macro definitions but does not write them to the output (e.g. they are removed from the source text). A macro invocation supplies arguments for the macro and is replaced in the output with the results of evaluating the macro text and substituting the arguments and (possibly) evaluating other macros. This is sometimes referred to as "macro expansion". Macros may be "nested" - e.g. the macro definition text may include invocations of other macros.
A simple macro with no arguments can be defined on a single line:
$macro PI 3.14
This creates a macro named "PI". When this macro is invoked, the invocation will be replaced with the macro text "3.14". The substitution will be done inline, meaning that the text resulting from the evaluation (in this case "3.14") will exactly replace the macro invocation, and any text on the same line before or after the invocation will be preserved. Multi-line macros (described below) replace the entire source line with one or more macro lines.
The macro definition may specify one or more argument names. Each argument must be supplied on the macro invocation (either by name, or by position -- see the invocation section below). Macro names follow the usual rules of variable naming (must start with a letter, cannot contain whitespace or other special characters).
$macro MS_TO_SAMPLES_48K(msec) ((${msec}/1000)/(1/48000))
This macro is defined to have one argument named "msec". The value of an argument supplied on an invocation will
be substituted into the macro text wherever the argument name is found enclosed in ${ and } as shown above. (Note that unlike
the C preprocessor, macro argument replacement has a different syntax than macro invocation so the intent is
clear, and in fact an argument may have the same name as another macro with no ambiguity).
Unlike the C preprocessor, there is no special syntax required to directly concatenate two arguments in the output text:
$macro MAKE_LABEL(prefix, name, postfix) ${prefix}${name}${postfix}
If this macro were invoked with the argument values "a", "b", and "c" the result would be "abc".
A macro definition may define a multi-line (as opposed to inline) macro. A multi-line macro replaces the entire
source line with one or more macro lines. A multi-line macro starts with $macro followed by the macro name,
optional list of arguments, and a ++ continuation indicator. The subsequent source lines, up to $endmacro
constitute the macro text.
$macro MULT_16(cr1, cr2, crTemp) ++
sl ${cr1},16 ; Move arg1 to upper 32 bits
cpy_cc ${crTemp},acc32 ; Save in temp
sl ${cr2},15 ; Move arg 2, not sure why 15 instead of 16 bits
multrr acc32,${crTemp} ; acc32 = upper 32 bits of 64 bit result
$endmacro
A macro definition argument may optionally include a direction indicator. This indicates to the user of the macro if the argument is an input, an output, or bidirectional. The direction indicator immediately follows the argument name. The postfix indicators are:
-
=>the argument is an output of the macro -
<=the argument is an input to the macro - '<=>' the argument is both an input and an output
Direction indicators are very useful when macro arguments are registers because it clarifies if the register is expected to have a value which is used in the macro, or it will contain a value returned from the macro (e.g. the execution of the macro code leaves a result in the register).
$macro COPY_CMX_TO_MR(mrTarget=>, crMRSource<=, crTemp=>) ++
${crTemp} = ${crMRSource}
${mrTarget} = ${crTemp}
$endmacro
In this macro definition the first argument is an output, the second is an input, and the third is
considered an output. Not only does this document the intent of the arguments, it can be used to validate
the macro invocation (see the invocation section below). In this example the crTemp argument is shown
as an output because as a temporary scratch register its value will be updated by the macro, it is just not a useful output.
Note that the TOON processor does not enforce or validate the direction indicators with the actual macro code. It is up to the macro developer to insure the argument direction indicators are accurate and represent the actual use of the arguments in the expanded macro.
If a macro definition argument has no direction indicator, it is presumed to have no defined direction and may be used as an input, output, or both.
In addition to the arguments defined in the macro definition, all macros have access to a set of 'virtual' arguments which use the same substitution syntax but are not explicitly passed on the macro invocation. These arguments provide access to special values created by the macro processor. Virtual macro argument names start with ":". Each is described below.
This substitutes a unique numeric value for each invocation of a macro. If this appears more than once in a macro definition it will substitute the same value for each appearance. This is useful to generate jump labels and unique names so that a macro may be used more than once in a single source file without creating duplicate labels and identifiers.
For example, the following macro generates code that contains a jmp instruction and the target location for it.
; Macro to subtract a fixed number of samples from the delay stored
; in an MR. If the result is less than zero, the delay is set to zero.
;
$macro SUBTRACT_DELAY(delayMR<=>, samples<=, crTemp=>) ++
;---START SUBTRACT_DELAY
cpy_cm ${crTemp}, ${delayMR} ; Get calculated delay
wrdld acc32, samples ; Get number of samples to deduct
subs ${crTemp}, acc32 ; Subtract deduction delay
jgez acc32, sub_ok_${:unique} ; If positive, continue
xor acc32, acc32 ; If neg, set to zero
sub_ok_${:unique}:
cpy_mc ${delayMR}, acc32 ; Store back adjusted delay
;---END SUBTRACT_DELAY
$endmacro
Note the use of ${:unique} to make the jump target and the corresponding label unique
so this macro can be used multiple times in the same source (or included) file. Since the
substituted value is unique across all macros and all invocations, it is OK if two different macros
use the same generated label names (e.g. another macro could define a label sub_ok_${:unique}
and it would not create any conflict with the macro above).
Also note the use of the <=> input/output indicator on the first argument which helps make it
clear that the delayMR is read by the macro, and also written by the macro. The second argument
is noted as an input and is not written. The last argument is a temp working register and is
noted as an output because its value will be altered by the macro.
This substitutes the name of the source file where this macro definition was created. This is only the file name and does not include the path.
This substitutes the name of root source file being processed (e.g. the first file on the command line). This is only the file name and does not include the path.
A macro invocation is indicated in the source text by a "$" character followed by the macro name, optionally followed by a comma-delimited list of values for the macro arguments. Macro names are case-insensitive. For example:
$MULT_16(r0, r1, r8)
Macro expansion starts with substituting the macro arguments with their
place holders ${argname} in the macro text. Then any macro invocations in the macro text (e.g.
nested macros) are expanded. And if a nested macro itself has macro invocations, they are also
expanded. Finally the macro invocation text is removed and replaced with the
substituted and expanded macro text. For a multi-line macro, the entire source line is
removed and replaced. Inline macros retain the surrounding text on the invocation line.
By the example above, the MULT_16 macro definition is multi-line, thus the line containing the macro invocation would be replaced with the following lines:
sl r0,16 ; Move arg1 to upper 32 bits
cpy_cc r8,acc32 ; Save in temp
sl r1,15 ; Move arg 2, not sure why 15 instead of 16 bits
multrr acc32,r8 ; acc32 = upper 32 bits of 64 bit result
The number of arguments supplied on a macro invocation must match the number of arguments
in the macro definition. If the macro allows optional arguments, those arguments can be left out as
in $MYMACRO(a, b, , , ) in which case the missing arguments are set to an empty string. Macros
that allow this must be specifically written to handle the case of the argument being empty. In any
case, the invocation must have the correct number of comma-delimiters for the correct number of arguments.
If a macro has no arguments the invocation may specify an empty list "()" or omit the list all together. The following invocations have the same effect:
$PI
$PI()
In some rare cases it will be necessary to use the empty argument list to delimit the macro name. If the macro invocation is immediately followed by a character which is a valid macro name character (e.g. letter or numeric) then the parens are necessary. For example if the PI macro is to be followed immediately by the letter "R", then this invocation will not work:
$PIR
The macro processor would fail to find a macro named "PIR". In this case the empty argument list will make it unambiguous:
$PI()R
This will correctly substitute the PI macro expansion, followed by the letter "R" for a result of:
3.14R
Macro argument values may be supplied in a positional or named format. Most common is the positional form where the supplied values are applied to the macro's argument list in the order they appear in the macro definition. This good when there are few arguments and their usage is clear, such as:
$DIV(x,y)
It is reasonably clear that "x" will be the numerator and "y" the divisor. However, when there is a long list of arguments and especially when multiple registers are passed as the values, the code becomes unclear:
$CALC_DELAY(r0, r6, r8, r9, r12)
It would be necessary to find and read the macro definition to have any idea about what the arguments are, and if the correct registers are being used. In this case the code can be made more readable by using named arguments in the invocation. In it's simplest form a named argument is the argument name (from the macro definition) followed by the "=" character, and then the argument value.
$CALC_DELAY(buffer_base=r0, offset=r6, cv=r8, tempReg1=r9, tempReg2=r12)
This makes for more self-documenting code, at the expense of a bit more typing. When using named arguments the order is not important, values are assigned to macro arguments by name, not position. The following is exactly equivalent to the example above:
$CALC_DELAY(tempReg1=r9, tempReg2=r12, offset=r6, buffer_base=r0, cv=r8)
Argument names must match the names used in the macro definition, but are not case sensitive. Positional and named arguments cannot be mixed in the same macro invocation.
Named arguments can also carry direction information. The use of named arguments makes matching registers to the correct macro arguments easy, but it does not say anything about how the registers are used (e.g. are they an input read by the macro, or an output written by the macro). Direction indicators provide a visual clue about how the argument is used and aid in understanding how to use the macro.
Direction indicators can be placed in the macro definition:
$macro COPY_SFR_TO_MR(mr=>, sfr<=) ++
acc32 = ${sfr}
${mr} = acc32
$endmacro
The => postfix on the first argument denotes this argument as an output, e.g. it's value will
be written (updated) by the macro. The second argument has a <= postfix indicating this is
an input argument and it's value will not be modified by the macro code.
The invocation of the macro can document the direction by using the same indicators in the named argument format:
$COPY_SFR_TO_MR(mr=>mr106, sfr<=TAPTEMPO)
The named argument syntax plus the direction indicator make it clear that the first argument is a memory register and it will be updated, and the second argument is a special function register that will be used as input to the macro.
If the direction indicator used on the macro invocation does not match the indicator of the macro definition, the macro processor will flag a syntax error. This helps insure the user of a macro has the same understanding as the author. For example, the following would be a syntax error:
$COPY_SFR_TO_MR(mr<=>mr106, sfr<=TAPTEMPO)
Using the named arguments and direction indicator features together helps makes the code more self-documenting and macro usage less error prone.
Predefined macros can be invoked like any other macro by the use of "$" followed by the macro name. The function of these macros is build into the FXCoreMP macro processor and can be used anywhere a macro expansion can be used.
All predefined macro names begin with an underscore to help distinguish them from your own macros.
The $_eval() predefined macro is an inline macro that can be used to evaluate mathematical or string constant expressions. Although the
FXCore assembler can also evaluate (math) expressions, it can only do so in specific places that expressions
are allowed, such as .EQU statements. The FXCoreMP _eval() macro can evaluate and substitute an expression
anywhere in the source code. This can be useful to construct source code from expression evaluation (for a
use case of this capability, see the MR Table Generation example on the Example Macros page).
The $_eval() macro takes a single argument which is the text of a math or string expression. The expression may use
symbolic names defined by earlier .EQU statements and may also include macro substitutions. For example:
.equ BASE_MR 40
$macro OFFSET 16
.mreg mr$_eval(BASE_MR + $OFFSET + 0) 0.947
.mreg mr$_eval(BASE_MR + $OFFSET + 1) 0.647
.mreg mr$_eval(BASE_MR + $OFFSET + 2) 0.112
The above would generate the following assembler code:
.equ BASE_MR 40
.mreg mr56 0.947
.mreg mr57 0.647
.mreg mr58 0.112
Mathematical expressions may include integer and decimal constants, all the usual operators (+,-,/,*,%,^), parenthesis to control evaluation order, and built-in functions including ABS(), FLOOR(), CEIL(), trig functions, and others (see reference below). If it is desired to force the result to be an integer, the FLOOR() and CEIL() functions can be used. For example:
$_eval(FLOOR(48000/56))
evaluates to 857. Without the FLOOR() it would evaluate to 857.142857142857.
The _eval() expression can also be a string expression using functions such as IF(), STR_SUBSTRING(), STR_LEFT(), and other string testing and manipulation functions. This can be used to test and transform strings which can be particularly useful in macro definitions where the arguments are always treated as strings. For example consider the following macro which encapsulates the assembler .RN and .MREG statements to create a symbolic memory register name and define its initial value:
$macro defMR(name, mreg, initVal) ++
.rn ${name} ${mreg}
.mreg ${name} ${initVal}
$endmacro
For this macro to generate syntactically correct code, the "mreg" argument must be of the form "mrNNN", e.g.
$defMR(mymask, mr27, 0xFFFF0000)
which will generate the following 2 assembler statements:
.rn mymask mr27
.mreg mymask 0xFFFF0000
But it may be desirable to allow the "mreg" argument to be specified as just the numeric "NNN" part ("27" in the example above), e.g. the following is a common pattern when the MR number is taken from a constant which must be a number (without the "mr" prefix) because it is used in later code to access the memory register:
.equ targetMR 27
$defMR(mymask, targetMR, 0xFFFF0000)
This would not work because the following invalid code would be generated:
.rn mymask 27
.mreg mymask 0xFFFF0000
The .RN statement is invalid. A string expression in the macro definition can check the "mreg" argument and generate the correct syntax no matter which form was passed in (e.g. "27" or "mr27"):
$macro defMR(name, mreg, initVal) ++
.rn ${name} mr$_eval(IF (STR_STARTS_WITH("${mreg}", "mr"), STR_SUBSTRING("${megr}", 2), "${mreg}"))
.mreg ${name} ${initVal}
$endmacro
This macro generates the .RN with the "mr" leading text, followed by the result of a string expression. The expression tests the first 2 character of the "mreg" argument. If it starts with "mr" then the numeric part is extracted, if it does not start with "mr" the argument is used as-is (it is presumed to be a number 0-127).
The IF() expression function and other string functions can be used to build some programming logic into the generation of source code.
Compatibility Notes: For best compatibility of your source code, when writing expressions note the following:
- When using IF() expressions that test for equality use the "==" operator, do not use a single "=" which can sometimes be incorrectly interpreted as an attempt to make an assignment.
- Use only lower-case function names, e.g. str_length(), ceiling(), etc. See the reference below for all possible functions.
For a complete reference of operators, functions and constants, see EvalEx Project.
This build-in macro can be used to build named counters used during source code generation. Counter values are numeric and can be positive or negative, integer or decimal. For example it can be used to create a counter that automatically assigns Memory Register symbols to sequential locations.
The $_count() macro is invoked with 3 arguments:
$_count(counter-name, operation, value)
The counter name identifies a specific counter. Counter names are global and are case insensitive. A counter is created
the first time the name is used in a $_counter() macro invocation. The initial value of a new counter is 0.
The operation
argument determines what is substituted into the source text in place of the macro and how (or if) the value of the
counter is updated. The operation argument must be one of:
ADD: This operation will add the value argument to the current value of the counter. The substitution value will be the
counter value before the addition (e.g. this behaves as a post-fix addition).
.rn mr$_count(myMRcount,add,1) datax
.rn mr$_count(myMRcount,add,1) datay
If the above was the first use of the $_count() macro with this name in the source code, it would generate:
.rn mr0 datax
.rn mr1 datay
After the 2nd line was generated the value of the counter would be 2.
INC: This is the same as ADD except that it generates no substitution value. This can be used to alter the value of
a counter without producing any source code. For example:
.rn mr$_count(myMRcount,add,1) table_of_8
$_count(myMRcount,inc,8)
This would generate one line of source with the .RN statement, and a second line which is empty:
.rn mr0 table_of_8
The counter would now contain 9.
SET: The counter value is set to the given value argument, and no substitution value is generated.
$_count(mycounter, set, 64)
GET: Substitutes the current value of the counter and leaves it unmodified. The value argument is unused may be omitted. For example:
.equ data_addr $_count(myMRcount,get,)
.rn mr$_count(myMRcount,add,1) data
This reserves an MR location for data and assigns a symbolic name to the numeric address. So upon first use
this would produce:
.equ data_addr 0
.rn mr0 data
A counter can use values other than integers, for example the following will produce a table of 1/8 interval values between 0.0 and 1.0. The $_eval() built in macro is used to calculate the interval.
.mreg mr10 $_count(eighths, add, $_eval(1/8))
.mreg mr11 $_count(eighths, add, $_eval(1/8))
.mreg mr12 $_count(eighths, add, $_eval(1/8))
.mreg mr13 $_count(eighths, add, $_eval(1/8))
.mreg mr14 $_count(eighths, add, $_eval(1/8))
.mreg mr15 $_count(eighths, add, $_eval(1/8))
.mreg mr16 $_count(eighths, add, $_eval(1/8))
.mreg mr17 $_count(eighths, add, $_eval(1/8))
The generated code:
.mreg mr10 0
.mreg mr11 0.125
.mreg mr12 0.25
.mreg mr13 0.375
.mreg mr14 0.5
.mreg mr15 0.625
.mreg mr16 0.75
.mreg mr17 0.875
This macro will write its argument to the console at the time the macro is expanded. It produces no source text. This can be useful for debugging macros or validation. This macro takes a single argument which is interpreted as a single string to be written to the console (after substitution of any embedded macros). Note that no quotes are required. Some examples:
$_log(Current value of mycounter is: $_count(mycounter,get,))
$ifenv debug=true
$_log(Building DEBUG)
$endenv
$ifenv debug!=true
$_log(Building PROD)
$endenv
The $setenv and $ifenv statements (along with command-line parameters) allow for conditional inclusion/exclusion of
blocks of lines from the source file. There are
multiple use cases but a common use is to conditionally include (or exclude) code based on some
value passed into the preprocessor at compile time. For example, it might be useful to include
extra debug code for development builds, but exclude that code when assembling for production distribution. They
might also be used to include or exclude optional feature or experimental code.
These statements operate on macro 'environment' variables defined either by $setenv statements or values passed
on the command line.
Note
These variables should not be confused with operating system environment variables. These variables are specific to the macro processor and are not related to any operating system concept of 'environment' variables.
Macro environment variables have a name and value. If a variable has not been defined
(e.g. no $setenv statement has created it, and it was not specified on the command line) then it's value
is assumed to be an empty string. Macro environment names and values are case-insensitive.
The $setenv statement has the following syntax:
$setenv <name>=<value>
The environment variable of the given name will be assigned the value on the right side of the "=". The
value will be trimmed of leading and trailing whitespace, and includes everything up to a comment
or the end of the line. The name cannot include blanks, parens, or the = character.
This is a simple text assignment, the value is not interpreted in any way (it cannot be a macro invocation). Alternatively, an environment variable may be assigned a value by passing an argument on the end of the command line when starting the processor. Values assigned on the command line must be simple strings with no embedded whitespace.
java ... -Ename1=value1 -Ename2=value2
A block of code to be conditionally included is enclosed by the $ifenv and $endenv statement. If the
condition on the $ifenv statement is true then the code block is included, otherwise it is omitted. There
are two forms of the $ifenv statement:
$ifenv varname = value
`$ifenv varname != value'
In the following example, a macro is defined one of two ways depending on a macro environment variable:
$ifenv debug=true
$macro GENERATE_DEBUG_DATA(fromLoc, length) ++
;
; ... code to generate some debug info ...
;
$endmacro
$endenv
$ifenv debug != true
$macro GENERATE_DEBUG_DATA(fromLoc, length) ++
; Do nothing
$endmacro
$endenv
When this macro is evaluated it will either create the debug generation code, or just a comment line depending on the macro environment variable. So invoking the processor with:
java ... -Edebug=true
will cause the macro to expand to the debug code, otherwise it will expand to a single comment line.
Currently, nested $ifenv statements are not supported.
Note that when $ifenv/$endenv is inside a macro definition it is evaluated only once when the macro definition is
first read, it is not evaluated each time the macro is expanded. So if a block is included or excluded by the
$ifenv, it will be included or excluded every time the macro is used (expanded).
The $include statement is used to embed lines from an external file into the source file (similar to the
C preprocessor #include directive). Once embedded they are treated
the same as original source (e.g. they are scanned and processed for macro statements). An included file itself may include additional files.
A file will be included only once (no need for conditional processing as with the #include C preprocessor directive). In the scope
of a single execution of the processor, a file will be included only once. Any additional includes of that file will be
skipped.
Included files are not limited to containing macro definitions, they may include executable assembler instructions as well. They are
inserted in place of the $include statement and then processed like all other source code.
Syntax:
$include filename
The file name is relative to the directory of the root source file being processed (e.g. the file specified as the first parameter of the command line). So if a plain file name is given with no path, it will be located in the same directory as the root source file.
Some known limitations of the macro processor:
- Block comments using
/* comment */that span multiple lines inside a macro definition do not appear in the expanded macro output. Single line comments using those delimiters will appear in the expanded output. - Macro definitions may not appear inside other macro definitions.
- Macro invocations may be nested to any level but must not be recursive (e.g. a macro must not invoke itself directly or or indirectly through other macros).
- Nested
$ifenvstatements are not supported.
Copyright © Cabintech Global LLC