12. Extended attributes

An attribute is a property attachable to functions, data objects, or types, specified as a keyword. Standard C includes built-in keywords like const and volatile.

Extended attributes provide access to behaviors or properties beyond Standard C. They are either target-specific or useful for embedded systems.

12.1. Overview

Attributes can be applied with either keyword syntax (e.g., __zpage) or attribute syntax (e.g., __attribute__((zpage))). Both are functionally equivalent, but the C parser may not accept the keyword form in some situations.

Usage is largely a matter of preference. Preprocessor macros can rename attributes for portability, enabling them to be toggled off for other targets or renamed to match different compilers or targets.

Note

The C parser sometimes produces unexpected errors with the keyword form of attributes (e.g., __zpage). If this occurs, use the __attribute__((zpage)) form instead.

12.2. Using attributes

Type attributes can be applied to type declarations, following the same syntax as type qualifiers like const and volatile.

Syntax for data objects

You can apply attributes to data objects as follows:

__attribute__((zpage)) int a, b;
int __zpage c, d;

When applied to an object, the attribute’s location is irrelevant. The example above applies the Zero page attribute to all defined objects (a, b, c, and d).

Syntax for pointer types

Attributes can also be applied to pointer types, where their location is significant. This determines whether the pointer itself is constant or what it points to is constant.

The easiest way to decipher attributes in function types is to read the type from right to left.

int __attribute__((zpage)) * p1;
long * __attribute__((zpage)) p2;

Here, p1 is a pointer stored in default memory that points to an int in Zero page memory. p2 is a pointer stored in Zero page memory that points to a long in default memory.

12.3. Attribute reference

This section goes through all available extension keywords and attributes.

Summary of attributes

The following table summarizes available attributes. For the keyword form, prefix with two underscores (e.g., __zpage). For attribute syntax, use the attribute name with __attribute__ (e.g., __attribute__((zpage))).

Table 12.1 Extended attributes summary

Attribute name

Description

aligned(nn)

Specifies alignment for data objects or functions

section("name")

Specifies the section name for a data object or function

zpage

Controls storage of data objects to the zero page

interrupt

Defines an interrupt function

kernal_interrupt

Defines a Commodore 64 kernal-friendly interrupt

intrinsic

Declares an intrinsic function

task

Relaxes preserving registers

far

Controls storage of data objects to far memory (MEGA65 only)

huge

Controls storage of data object to huge memory (MEGA65 only)

Description of attributes

This section describes each attribute in detail.

aligned

This attribute can be applied to functions, global and static data objects to force a certain minimal alignment.

The aligned attribute takes an argument which is the alignment to use:

__attribute__((aligned(16))) struct sprite ship;

Note

Certain data types may impose an alignment by themselves. The actual alignment is choosen so that all alignment constraints are satisfied.

section

This attribute can be applied to functions, global and static data objects to control the name of the section it is placed in.

The section attribute takes an argument which is the section name to use:

// Place in vram
__attribute__((section("vram")))
const char tiles[256] = { .. };

__attribute__((section("trueCode")))
long foo () {
  return 42;
}

See Description of pragma directives for how you can specify a section for multiple functions, global and static data objects.

zpage

Specifies a data object or a pointer to a data object residing in the zero page. Zero page addressing yields shorter, faster code and is particularly efficient for 6502 pointer operations.

Note

Auto variables are typically allocated to registers or zero page pseudo-registers, automatically providing the benefits of zero page addressing. The zpage attribute is best suited for smaller, frequently used static data objects.

far

The far attribute specifies data in memory beyond the 64K provided by the base 6502. It is available only when the MEGA65 is enabled.

On the MEGA65, the 45GS02 CPU provides additional addressing capabilities. The amount of far data objects is limited only by available memory. A single far object can be at most 65535 bytes large.

huge

The huge attribute specifies data in memory beyond the 64K provided by the base 6502. It is available only when the MEGA65 is enabled.

On the MEGA65, the 45GS02 CPU provides additional addressing capabilities. The amount of huge data objects is limited only by available memory.

Note

The huge attribute typically generates larger applications than the far attribute. Use huge only if absolutely necessary.

interrupt

An interrupt function serves as an interrupt handler and has the following effects:

  1. An interrupt function cannot take parameters

  2. It preserves all registers used.

  3. Exiting the interrupt function uses a different instruction sequence than normal functions

  4. The interrupt attribute may optionally be given a vector address as an argument

The interrupt vector is specified as an argument to the interrupt attribute:

int counter;

__attribute__((interrupt(0xfffe)))
void irq () {
  counter++;
}

Note

The vector argument is optional. Omitting it results in no vector section entry being generated for that interrupt function. All vector sections can also be suppressed using the --no-vector-sections command-line option.

Note

It is strongly recommended to keep interrupt functions small and simple. Avoid making function calls from an interrupt function, as this requires preserving scratch registers, which is costly. If function calls are necessary, consider forcing them to be inline expanded. See Function inlining for more information.

kernal_interrupt

The kernal_interrupt attribute defines an interrupt function for the Commodore 64 kernal. It can also be used by Commander X16, as they share the same mechanism.

Kernal interrupt functions must be installed using the _Kernal_set_interrupt_handler() function, included in the C library.

#include <kernal/interrupt.h>

__attribute__((kernal_interrupt))
void myirq () {
  counter++;
}

int main () {
  __kernal_vector_t old_vector;

  old_vector = _Kernal_set_interrupt_handler(myirq);

  ... application code

  _Kernal_restore_interrupt_handler(old_vector);
  return 0;
}

Installing a new vector writes it to location 0x0314 and saves the previous one in a library-internal variable. The kernal interrupt function exits by jumping via this saved pointer.

Before exiting your application, restore the old vector.

intrinsic

The intrinsic attribute declares built-in functions. This applies only to intrinsic functions already known to the compiler. Typically, you use this by including the calypsi/intrinsics6502.h file, which contains all such valid declarations.

task

The task attribute can be applied to functions like main, which mark the application entry point. Such functions are typically not called from C code. Applying the task attribute relaxes register preservation, potentially saving stack space and reducing application size by a small amount.