< back to index

Macros and inlining

Macros

Functions defined with the macro keyword are not actual functions, but they are used as syntax replacements.

It implies the following:

  • macros must return void

  • cannot be inline, noinline or extern

  • cannot contain variable or array declarations

  • but can contain scalar constant declarations; the constants are scoped to the particular macro invocation

  • can be asm - in this case, they should not end with a return instruction

  • do not have an address

  • their invocations cannot be used as expressions

  • in case of asm macros, the parameters:

    • must be defined as either const (compile-time constants), ref (variables) or register(XX) (registers, where XX is the register you want to use)

    • at most one parameter can be defined as a register

  • in case of non-asm macros, the parameters

    • must be defined as either ref (variables; default, may be omitted) const (compile-time constants), or call (expressions, which are evaluated every time they are used)

    • ref parameters exceptionally can have their type declared as void; such parameters accept variables of any type

    • call parameters exceptionally can have their type declared as void; such parameters accept expressions of any type, including void, however, you cannot assign from those expressions

  • macros do not have their own scope (they reuse the scope from their invocations) – exceptions:

  • the parameters

  • the local labels defined in assembly

  • the local constants

  • control-flow statements (break, continue, return, goto, label) are run as if places in the caller function

When invoking a macro, you need to pass variables as arguments to parameters annotated with ref and constants as arguments annotated with const.

Invoking a non-asm macro requires the types of variables via ref parameters to match precisely. No type conversions are performed. Exception: parameters of type void can accept a variable of any type.

For parameters defined as const, register(XX) or call, the usual type conversions are performed.

You can invoke a macro from assembly, by preceding the invocation with +

Examples:

macro void inc_x() {
    x += 1
}
byte add_two_1(byte x) {
    inc_x()
    inc_x()
    return x
}

macro void inc(byte b) {
    b += 1
}
byte add_two_2(byte x) {
    inc(x)
    inc(x)
    return x
}
macro void perform_twice(void call f) {
    f
    f
}
byte add_two_3(byte x) {
    perform_twice(inc(x))
    return x
}

macro void add(byte b, byte v) {
    b += v
}
macro void retu(byte result) {
    return result
}
byte add_two_4(byte x) {
    add(x, 2)
    retu(x)
}

macro asm byte add_asm(byte ref b, byte const v) {
    LDA b
    CLC
    ADC #v
    STA b
    // no RTS!
}
byte add_two_5(byte x) {
    add_asm(x, 2)
    return x
}

macro asm byte add_asm_2(byte ref b, byte register(x) v) {
    TXA
    CLC
    ADC b
    STA b
    // no RTS!
}
byte add_two_6(byte x) {
    add_asm_2(x, 2)
    return x
}

Automatic inlining

You can control inlining behavior in several ways:

  • functions declared with the const keyword called with constant arguments will always be inlined, with the whole invocation being converted into a single constant, regardless of inline and noinline keywords; calls with non-constant arguments are subject to the regular rules.

  • functions declared with the inline keyword will be inlined if possible

  • functions declared with the noinline keyword will never be inlined

  • the remaining functions may be inlined only if the -finline command-line option is enabled, and the compiler decides the function is worth doing

Automatic subroutine extraction

Subroutine extraction is the opposite of inlining.

When given the -fsubroutine-extraction, the compiler will attempt to extract common code fragments to new subroutines. The code will get smaller and slower.

Generally, when using -fsubroutine-extraction, it's recommended to also use -finline. This allows the compiler to first inline and optimize code and then extract it back when appropriate.