Skip to main content

Goal: Understanding JALR instruction and “Instructions are data” through the concept of Function Pointers

What Are Function Pointers?

  • Definition: A function pointer is a variable that stores the address of a function that can be called later through the pointer.
  • Usage: Function pointers allow programs to select functions to execute at runtime, enabling dynamic behavior and flexible code design.
  • Syntax in C/C++:
    return_type (*pointer_name)(parameter_types);
    

Why Use Function Pointers?

  • Callback Mechanisms: Pass functions as arguments to other functions.
  • Dynamic Dispatch: Select functions to execute based on conditions at runtime.
  • Implementing Polymorphism: Achieve behavior similar to virtual functions without inheritance.

Example Usage

void foo() { /*...*/ }
void bar() { /*...*/ }

void (*func_ptr)();

if (condition) {
    func_ptr = foo;
} else {
    func_ptr = bar;
}

func_ptr(); // Calls either foo or bar based on the condition

2. Function Pointers in Your Code

Overview of the Code

  • Purpose: Demonstrates the use of function pointers to print data from nodes in a linked list without explicit type checking.
  • Structures Defined:
    • Student: Contains a name field and a static print method.
    • Employee: Contains an id field and a static print method.
    • Node: Holds a generic data pointer, a print function pointer, and a next pointer for the linked list.

Key Components

Student Struct

typedef struct Student {
    char name[50];

    static void print(void *node) {
        Student *s = (Student *)node;
        printf("Student:\n");
        printf("Name: %s\n", s->name);
    }
} Student;

Employee Struct

typedef struct Employee {
    int id;

    static void print(void *node) {
        Employee *e = (Employee *)node;
        printf("Employee:\n");
        printf("Employee ID: %d\n", e->id);
    }
} Employee;

Node Struct

typedef struct node {
    void *data;
    struct node *next;
    int type;
    void (*print)(void *);
} Node;

Adding Nodes to the Linked List

  • Function: addNode(Node *head, void *data, void (*printFunction)(void *), int type)
  • Purpose: Creates a new node with the given data and function pointer, and adds it to the end of the list.
  • Usage:

    Student s1 = {"Alice"};
    head = addNode(head, &s1, Student::print, STUDENT);
    
    Employee e1 = {25};
    head = addNode(head, &e1, Employee::print, EMPLOYEE);
    
+----------------+       +----------------+
|    Node 1      |       |    Node 2      |
|  (Student)     |       |  (Employee)    |
+----------------+       +----------------+
| data           |  ---> | data           |  --->
| next           |  ---  | next           |  ---> NULL
| type: STUDENT  |       | type: EMPLOYEE |
| print          |       | print          |
+----------------+       +----------------+

Details:

Node 1:
- data points to a Student object:
  +-----------------+
  | Student         |
  |-----------------|
  | name: "Alice"   |
  +-----------------+
- next points to Node 2
- type: STUDENT (0)
- print points to Student::print function

Node 2:
- data points to an Employee object:
  +-----------------+
  | Employee        |
  |-----------------|
  | id: 25          |
  +-----------------+
- next is NULL (end of list)
- type: EMPLOYEE (1)
- print points to Employee::print function
Node *temp = head;

while (temp) {
    temp->print(temp->data);
    printf("\n");
    temp = temp->next;
}
  • Explanation:
    • temp->print: Function pointer to the appropriate print function (Student::print or Employee::print).
    • temp->data: Pointer to the data (Student or Employee object).
    • Process: Calls the print function without checking the type, leveraging function pointers for dynamic dispatch.

3. How RISC-V Instructions Handle Function Pointers

Function Calls in RISC-V

  • Direct Calls: Use jal (Jump and Link) with a label or address known at compile time.
  • Indirect Calls (Function Pointers): Use jalr (Jump and Link Register) with the function’s address stored in a register.

Registers Involved

  • a0 to a7: Argument and return value registers.
  • ra: Return address register.
  • s0: Frame pointer (used to access local variables on the stack).

Assembly Code Corresponding to the Print Loop

.LBB1_8:
    lw      a0, -76(s0)     # Load 'temp' from the stack into 'a0'
    lw      a1, 12(a0)      # Load 'temp->print' into 'a1'
    lw      a0, 0(a0)       # Load 'temp->data' into 'a0'
    jalr    a1              # Call the function pointed by 'a1' with 'a0' as argument

Detailed Explanation of the Assembly Sequence

  1. Load the temp Pointer

    lw      a0, -76(s0)
    
    • Action: Load the value of temp from the (offset -76 from s0) into register a0. s0 is base address of temp object
    • Purpose: Access the current node in the linked list.
  2. Load the Function Pointer (temp->print)

    lw      a1, 12(a0)
    
    • Action: Load the function pointer from offset 12 within the Node struct into register a1.
      • Assuming the Node struct layout:
        • data at offset 0
        • next at offset 4 (assuming 32-bit pointers)
        • type at offset 8
        • print at offset 12
    • Purpose: Retrieve the address of the function to call.
  3. Load the Data Pointer (temp->data)

    lw      a0, 0(a0)
    
    • Action: Load the data pointer from offset 0 within the Node struct into register a0.
    • Purpose: Prepare the argument for the function call.
  4. Call the Function via Function Pointer

    jalr    a1
    
    • Action: Jump to the address in a1 (the function pointer) and link (store return address in ra).
    • Purpose: Perform the indirect function call using the function pointer.

Connecting Assembly to C++ Code

  • C++ Code:

    temp->print(temp->data);
    
  • Assembly Translation:

    1. Load temp: lw a0, -76(s0)
    2. Load print function pointer: lw a1, 12(a0)
    3. Load data pointer: lw a0, 0(a0)
    4. Call function via pointer: jalr a1
  • Process:

    • Argument Passing: In RISC-V calling convention, the first argument is passed in a0.
    • Function Call: jalr a1 calls the function whose address is in a1.

4. Execution Flow of the Print Loop

Step-by-Step Execution

  1. Initialization

    • temp is set to point to the head of the linked list.
  2. Loop Condition Check

    • The loop continues as long as temp is not NULL.
  3. Function Pointer Invocation

    • Load temp into a0:
      • Access the current node.
    • Load temp->print into a1:
      • Retrieve the function pointer.
    • Load temp->data into a0:
      • Prepare the function argument.
    • Call Function via jalr a1:
      • Perform the indirect function call.
  4. Advance to Next Node

    • Update temp to temp->next to proceed to the next node in the list.
  5. Repeat

    • The loop repeats until temp becomes NULL.

Visualization of Registers and Memory

  • Before Function Call:
    • a0: temp->data (argument for print function)
    • a1: temp->print (address of print function)
  • Function Call:
    • jalr a1 jumps to temp->print.
    • Return address is stored in ra.
  • After Function Execution:
    • Control returns to the instruction following jalr a1.

Goal: Understanding “Instructions are data” through self modifying code