Jump Labels
Jump Labels are a simple abstraction included into the language to make defining
and referring to JUMPDEST
s more simple for the developer.
Example
#define macro MAIN() = takes (0) returns (0) {
// Store "Hello, World!" in memory
0x48656c6c6f2c20576f726c6421
0x00 mstore // ["Hello, World!"]
// Jump to success label, skipping the revert statement
success // [success_label_pc, "Hello, World!"]
jump // ["Hello, World!"]
// Revert if this point is reached
0x00 0x00 revert
// Labels are defined within macros or functions, and are designated
// by a word followed by a colon. Note that while it may appear as if
// labels are scoped code blocks due to the indentation, they are simply
// destinations to jump to in the bytecode. If operations exist below a label,
// they will be executed unless the program counter is altered or execution is
// halted by a `revert`, `return`, `stop`, or `selfdestruct` opcode.
success:
0x00 mstore
0x20 0x00 return
}
Label Scoping
Huff-Neo implements scoped label resolution to prevent situations where multiple macro invocations defining the same label would cause all jumps to incorrectly target the last definition.
Scoping Rules
Label Definition
- Each label is associated with the scope where it's defined
- A scope is determined by the macro invocation chain
- Labels defined directly in a macro belong to that macro's scope
- Labels defined in invoked macros belong to the invoked macro's scope
Duplicate Detection
- Within Same Scope: Duplicate labels in the same scope will cause a compilation error
- Across Different Scopes: Labels with the same name in different scopes are allowed (shadowing)
Label Resolution
Labels are resolved in the following order:
- Current and Parent Scopes: Searches from the current scope upward through parent scopes
- Child Scopes: If not found, searches in child scopes (macros invoked from current scope)
- Sibling Scopes: As a fallback, searches in sibling scopes (macros invoked from the same parent)
This resolution order allows:
- Inner scope labels to shadow outer scope labels
- Parent macros to reference labels defined in nested child macros
- Sibling macros to cross-reference each other's labels when needed
Examples
Duplicate Labels (Error)
#define macro MAIN() = takes(0) returns(0) {
my_label: // First definition
0x01
my_label: // ERROR: Duplicate label in same scope
0x02
}
Label Shadowing (Allowed)
#define macro INNER() = takes(0) returns(0) {
my_label: // Label in INNER scope
0x01
my_label // Jumps to INNER's my_label
}
#define macro MAIN() = takes(0) returns(0) {
my_label: // Label in MAIN scope
0x00
INNER() // INNER has its own my_label
my_label // Jumps to MAIN's my_label
}
Multiple Macro Invocations
#define macro DEF_LBL() = takes(0) returns(0) {
my_label:
0x42
my_label // Jumps to THIS invocation's my_label
}
#define macro MAIN() = takes(0) returns(0) {
DEF_LBL() // First invocation - creates my_label in its scope
DEF_LBL() // Second invocation - creates my_label in its own scope
// Both invocations work correctly with their own labels
}
Parent Accessing Child Labels
#define macro INNER() = takes(0) returns(0) {
inner_label: // Label defined in nested macro
0x42
}
#define macro OUTER() = takes(0) returns(0) {
INNER() // Invoke macro that defines inner_label
}
#define macro MAIN() = takes(0) returns(0) {
OUTER()
inner_label jump // Parent can jump to label in nested child
}
Sibling Cross-References
#define macro FIRST() = takes(0) returns(0) {
first_label:
0x01
}
#define macro SECOND() = takes(0) returns(0) {
first_label jump // Can reference sibling's label
}
#define macro MAIN() = takes(0) returns(0) {
FIRST() // Defines first_label
SECOND() // References first_label from FIRST
}
Labels as Macro Arguments
When labels are passed as arguments to macros, they are resolved from the perspective of the macro that's passing them, not the macro that's using them. This allows labels to be accessed across different scopes:
#define macro SET_LABEL() = takes(0) returns(0) {
my_label:
0x42
}
#define macro USE_LABEL(lbl) = takes(0) returns(0) {
<lbl> jump // Uses the label passed as argument
}
#define macro WRAPPER(m) = takes(0) returns(0) {
USE_LABEL(<m>) // Pass label as argument to USE_LABEL
}
#define macro MAIN() = takes(0) returns(0) {
SET_LABEL() // Define my_label in SET_LABEL's scope
WRAPPER(my_label) // Pass label through nested invocations
}
In this example:
my_label
is defined inSET_LABEL
's scope (a child of MAIN)- When
MAIN
callsWRAPPER(my_label)
, the label is resolved from MAIN's perspective - MAIN can see
my_label
because it's defined in a child scope (SET_LABEL) WRAPPER
passes the label toUSE_LABEL
as<m>
- When
USE_LABEL
uses<lbl>
, it jumps to the correct label
This works even with deeply nested invocations:
#define macro DEEP_USE(label) = takes(0) returns(0) {
<label> jump
}
#define macro LEVEL2(lbl) = takes(0) returns(0) {
DEEP_USE(<lbl>) // Pass through another level
}
#define macro LEVEL1(l) = takes(0) returns(0) {
LEVEL2(<l>) // Pass through to LEVEL2
}
#define macro MAIN() = takes(0) returns(0) {
target:
0x42
LEVEL1(target) // Label resolved from MAIN's scope
}
The key point is that label arguments (<label>
) are resolved where they are passed, not where they are used. This enables flexible label passing between macros at different scope levels.
Label Shadowing with Arguments
When passing labels as arguments, shadowing rules still apply. Inner scope labels shadow outer scope labels:
#define macro USE_LABEL(lbl) = takes(0) returns(0) {
<lbl> jump
}
#define macro INNER() = takes(0) returns(0) {
target: // Inner scope's target
0x01
USE_LABEL(target) // Resolves to INNER's target (0x01)
}
#define macro MAIN() = takes(0) returns(0) {
target: // MAIN's target
0x00
INNER() // INNER defines its own target
USE_LABEL(target) // Resolves to MAIN's target (0x00)
}
In this example:
INNER
defines its owntarget
label that shadowsMAIN
'starget
- When
INNER
passestarget
toUSE_LABEL
, it resolves to INNER's label - When
MAIN
passestarget
toUSE_LABEL
, it resolves to MAIN's label - Each macro sees and passes its own version of the label
This shadowing behavior is useful when you want macros to work with their local labels without worrying about naming conflicts:
#define macro JUMP_TO(lbl) = takes(0) returns(0) {
<lbl> jump
}
#define macro PROCESS() = takes(0) returns(0) {
loop: // Local loop label
0x01 add
dup1 0x10 lt
loop JUMP_TO // Uses local loop, not affected by other loops
}
#define macro MAIN() = takes(0) returns(0) {
loop: // MAIN's loop label
PROCESS() // PROCESS has its own loop
loop jump // Jumps to MAIN's loop
}
Best Practices
- Use unique label names within a macro to avoid confusion
- Be aware that macro invocations create new scopes
- Use label shadowing intentionally and document it when used
- Consider prefixing labels with macro names for clarity in complex contracts