Given a very basic linked list in C:
struct node {
int value;
struct node *next;
};
I want to write the function "map" in RISC-V assembly language. The map function recursively applies a function to change the value at each node in the list. Ex: square all the data in the linked list. Function in C for reference:
void map(struct node *head, int (*f)(int))
{
if (!head) { return; }
head->value = f(head->value);
map(head->next,f);
}
The test output is as follows: prints the data in linked list, calls map with the function pointer called "square" which will square each value, then calling map again to a function pointer named "decrement" which will decrease each value by 1.
9 8 7 6 5 4 3 2 1 0
81 64 49 36 25 16 9 4 1 0
80 63 48 35 24 15 8 3 0 -1
Here is my RISC-V code that I am running in Venus, however I am having an issue with line 76 when I try and call the function passed into map.
.globl map
.text
main:
jal ra, create_default_list
add s0, a0, x0 # a0 (and now s0) is the head of node list
# Print the list
add a0, s0, x0
jal ra, print_list
# Print a newline
jal ra, print_newline
# === Calling `map(head, &square)` ===
# Load function arguments
add a0, s0, x0 # Loads the address of the first node into a0
# Load the address of the "square" function into a1 (hint: check out "la" on the green sheet)
### YOUR CODE HERE ###
la a1, square
# Issue the call to map
jal ra, map
# Print the squared list
add a0, s0, x0
jal ra, print_list
jal ra, print_newline
# === Calling `map(head, &decrement)` ===
# Because our `map` function modifies the list in-place, the decrement takes place after
# the square does
# Load function arguments
add a0, s0, x0 # Loads the address of the first node into a0
# Load the address of the "decrement" function into a1 (should be very similar to before)
### YOUR CODE HERE ###
# Issue the call to map
jal ra, map
# Print decremented list
add a0, s0, x0
jal ra, print_list
jal ra, print_newline
addi a0, x0, 10
ecall # Terminate the program
map:
# Prologue: Make space on the stack and back-up registers
### YOUR CODE HERE ###
addi sp, sp, -8
sw ra, 4(sp)
sw a0, 0(sp)
beq a0, x0, done # If we were given a null pointer (address 0), we're done.
add s0, a0, x0 # Save address of this node in s0
add s1, a1, x0 # Save address of function in s1
# Remember that each node is 8 bytes long: 4 for the value followed by 4 for the pointer to next.
# What does this tell you about how you access the value and how you access the pointer to next?
# Load the value of the current node into a0
# THINK: Why a0?
### YOUR CODE HERE ###
lw a0, 0(s0)
# Call the function in question on that value. DO NOT use a label (be prepared to answer why).
# Hint: Where do we keep track of the function to call? Recall the parameters of "map".
### YOUR CODE HERE ###
jalr ra, 0(a1)
# Store the returned value back into the node
# Where can you assume the returned value is?
### YOUR CODE HERE ###
sw a0 0(s0)
# Load the address of the next node into a0
# The address of the next node is an attribute of the current node.
# Think about how structs are organized in memory.
### YOUR CODE HERE ###
lw t0 4(s0)
mv a0 t0
# Put the address of the function back into a1 to prepare for the recursion
# THINK: why a1? What about a0?
### YOUR CODE HERE ###
mv a1 s1
# Recurse
### YOUR CODE HERE ###
jal ra, map
lw ra, 4(sp)
lw s0, 0(sp)
addi sp, sp, 8
jr ra
done:
# Epilogue: Restore register values and free space from the stack
### YOUR CODE HERE ###
addi sp, sp, -8
jr ra # Return to caller
# === Definition of the "square" function ===
square:
mul a0, a0, a0
jr ra
# === Definition of the "decrement" function ===
decrement:
addi a0, a0, -1
jr ra
# === Helper functions ===
# You don't need to understand these, but reading them may be useful
create_default_list:
addi sp, sp, -12
sw ra, 0(sp)
sw s0, 4(sp)
sw s1, 8(sp)
li s0, 0 # Pointer to the last node we handled
li s1, 0 # Number of nodes handled
loop: # do...
li a0, 8
jal ra, malloc # Allocate memory for the next node
sw s1, 0(a0) # node->value = i
sw s0, 4(a0) # node->next = last
add s0, a0, x0 # last = node
addi s1, s1, 1 # i++
addi t0, x0, 10
bne s1, t0, loop # ... while i!= 10
lw ra, 0(sp)
lw s0, 4(sp)
lw s1, 8(sp)
addi sp, sp, 12
jr ra
print_list:
bne a0, x0, print_me_and_recurse
jr ra # Nothing to print
print_me_and_recurse:
add t0, a0, x0 # t0 gets current node address
lw a1, 0(t0) # a1 gets value in current node
addi a0, x0, 1 # Prepare for print integer ecall
ecall
addi a1, x0, ' ' # a0 gets address of string containing space
addi a0, x0, 11 # Prepare for print char syscall
ecall
lw a0, 4(t0) # a0 gets address of next node
jal x0, print_list # Recurse. The value of ra hasn't been changed.
print_newline:
addi a1, x0, '\n' # Load in ascii code for newline
addi a0, x0, 11
ecall
jr ra
malloc:
addi a1, a0, 0
addi a0, x0, 9
ecall
jr ra
.data
start_msg: .asciiz "List before: "
end_msg: .asciiz "List after: "
.text
main:
jal ra, create_default_list
add s0, a0, x0 # $v0 = $s0 is head of node list
#print the list
add a0, s0, x0
jal ra, print_list
# print a newline
jal ra, print_newline
# load your args
add a0, s0, x0 # load the address of the first node into $a0
# load the address of the function in question into $a1 (check out la)
### YOUR CODE HERE ###
la a1,square
# issue the call to map
jal ra, map
#print the list
add a0, s0, x0
jal ra, print_list
addi a0, x0, 10
ecall #Terminate the program
map:
# Prologue: Make space on the stack and back-up registers
### YOUR CODE HERE ###
addi sp, sp, -12
sw ra, 0(sp)
sw s0, 4(sp)
sw s1, 8(sp)
beq a0, x0, done # If we were given a null pointer (address 0), we're done.
add s0, a0, x0 # Save address of this node in s0
add s1, a1, x0 # Save address of function in s1
# Remember that each node is 8 bytes long: 4 for the value followed by 4 for the pointer to next.
# What does this tell you about how you access the value and how you access the pointer to next?
# load the value of the current node into a0
# THINK: why a0?
### YOUR CODE HERE ###
lw a0 ,0(s0)
# Call the function in question on that value. DO NOT use a label (be prepared to answer why).
# What function? Recall the parameters of "map"
### YOUR CODE HERE ###
jalr s1
# store the returned value back into the node
# Where can you assume the returned value is?
### YOUR CODE HERE ###
sw a0, 0(s0)
# Load the address of the next node into a0
# The Address of the next node is an attribute of the current node.
# Think about how structs are organized in memory.
### YOUR CODE HERE ###
lw a0, 4(s0)
# Put the address of the function back into a1 to prepare for the recursion
# THINK: why a1? What about a0?
### YOUR CODE HERE ###
add a1, s1, x0
# recurse
### YOUR CODE HERE ###
jal ra map
done:
# Epilogue: Restore register values and free space from the stack
### YOUR CODE HERE ###
addi sp, sp, 12
lw ra, 0(sp)
lw s0, 4(sp)
lw s1, 8(sp)
jr ra # Return to caller
square:
mul a0 ,a0, a0
jr ra
create_default_list:
addi sp, sp, -12
sw ra, 0(sp)
sw s0, 4(sp)
sw s1, 8(sp)
li s0, 0 # pointer to the last node we handled
li s1, 0 # number of nodes handled
loop: #do...
li a0, 8
jal ra, malloc # get memory for the next node
sw s1, 0(a0) # node->value = i
sw s0, 4(a0) # node->next = last
add s0, a0, x0 # last = node
addi s1, s1, 1 # i++
addi t0, x0, 10
bne s1, t0, loop # ... while i!= 10
lw ra, 0(sp)
lw s0, 4(sp)
lw s1, 8(sp)
addi sp, sp, 12
jr ra
print_list:
bne a0, x0, printMeAndRecurse
jr ra # nothing to print
printMeAndRecurse:
add t0, a0, x0 # t0 gets current node address
lw a1, 0(t0) # a1 gets value in current node
addi a0, x0, 1 # prepare for print integer ecall
ecall
addi a1, x0, ' ' # a0 gets address of string containing space
addi a0, x0, 11 # prepare for print string syscall
ecall
lw a0, 4(t0) # a0 gets address of next node
jal x0, print_list # recurse. We don't have to use jal because we already have where we want to return to in ra
print_newline:
addi a1, x0, '\n' # Load in ascii code for newline
addi a0, x0, 11
ecall
jr ra
malloc:
addi a1, a0, 0
addi a0, x0 9
ecall
jr ra
I have tried https://godbolt.org/ and all the different conversions to RISC-V but it still errors.
for example my C code:
#include <stdio.h>
int main(){
printf("test");
return 0;
}
converts to:
main: # #main
addi sp, sp, -16
sw ra, 12(sp) # 4-byte Folded Spill
sw s0, 8(sp) # 4-byte Folded Spill
addi s0, sp, 16
mv a0, zero
sw a0, -16(s0) # 4-byte Folded Spill
sw a0, -12(s0)
lui a0, %hi(.L.str)
addi a0, a0, %lo(.L.str)
call printf
lw a0, -16(s0) # 4-byte Folded Reload
lw s0, 8(sp) # 4-byte Folded Reload
lw ra, 12(sp) # 4-byte Folded Reload
addi sp, sp, 16
ret
.L.str:
.asciz "test"
it gives error at "lui" because it expected 2 arguments but received 3. I tried organizing the code a bit by using .data section and .text section but still no work.
Use la $a0, .L.str instead of your 2-instruction %hi,%lo combo.
Also, use .string instead of .asciz.
This is the C source code
#include <stdio.h>
int main() {
printf("The Factorial of 10 is %d\n", fact(10));
}
int fact(int n) {
if (n < 1)
return (1);
else
return (n * fact(n - 1));
}
I am converting a C Programming function to a MIPS, but when I run the MIPS program I am getting an error for the .ascii section.
.text
.globl main
main:
subu $sp,$sp,32 # Stack frame is 32 bytes long
sw $ra,20($sp) # Save return address
sw $fp,16($sp) # Save old frame pointer
addiu $fp,$sp,28 # Set up frame pointer
li $a0,10 # Put argument (10) in $a0
jal fact # Call factorial function
la $a0,$LC # Put format string in $a0
move $a1,$v0 # Move fact result to $a1
jal printf # Call the print function
lw $ra,20($sp) # Restore return address
lw $fp,16($sp) # Restore frame pointer
addiu $sp,$sp,32 # Pop stack frame
jr $ra # Return to caller
.rdata
$LC:
.ascii “The factorial of 10 is %d\n\000”
.text
fact:
subu $sp,$sp,32 # Stack frame is 32 bytes long
sw $ra,20($sp) # Save return address
sw $fp,16($sp) # Save frame pointer
addiu $fp,$sp,28 # Set up frame pointer
sw $a0,0($fp) # Save argument (n) to use for Recursive Call
lw $v0,0($fp) # Load n
bgtz $v0,$L2 # Branch if n > 0
li $v0,1 # Return 1
jr $L1 # Jump to code to return
$L2:
lw $v1,0($fp) # Load n
subu $v0,$v1,1 # Compute n - 1
move $a0,$v0 # Move value to $a0
jal fact # Call factorial function
lw $v1,0($fp) # Load n
mul $v0,$v0,$v1 # Compute fact(n-1) * n
$L1: # Result is in $v0
lw $ra, 20($sp) # Restore $ra
lw $fp, 16($sp) # Restore $fp
addiu $sp, $sp, 32 # Pop stack
jr $ra # Return to caller
It's giving me an error for the .ascii code section saying it shouldn't be in the .text:
Error in ".ascii" directive cannot appear in text segment
It's also saying that:
"$L1": operand is of incorrect type
It's giving me an error for the .ascii code section saying it shouldn't be in the .text:
Error in ".ascii" directive cannot appear in text segment"
I am going out on a limb here because I am not 100% sure what you are running this on, but some sims like MARS don't recognize the rdata segment. You can try using just .data.
Also, if you are on something like WinMIPS64, you may want to try placing the .data segment at the top of the code. I understand what you are doing is right in some environments and but doesn't work in others, so give it a whirl.
May I suggest you try these things separately, just in case.
C-code :
int factorial(int n)
{
if (n<1) return 1;
else return n * factorial (n-1);
}
I've tried to implement it but haven't managed to do a lot. So here is my try:
goto:
factorial:
int factorial(int n) {
if (n<1) goto Lthen;
Lelse:
tmp=factorial(n-1);
return n*tmp;
goto Lend;
Lthen: return 1;
Lend;
}
RISC V:
.factorial
addi sp, sp, -16
sw ra, (sp)
sw s0, 4(sp) //n
sw s1, 8(sp) //tmp
mv s0, a0 //a--->s0
addi t1, zero,1
blt s0, t1, Lthen
.Lelse
mv t0, s0 // copy of n to t0
addi s0, s0, -1 // n-1
mv a0, s0; // n-1--->a0
jal factorial // factorial(a0)
mv s1, a0 // s1=factorial(a0) //tmp
mul a0,t0,s1 // n*tmp ----> a0
ret
j LEND
Lthen: li a0,1
ret
LEND jr ra, 0
Can somebody tell me is that okay, because I don't know how to test it.
And I am not sure about for example return 1 /or any other value/expression, can we just put it in a0 and say ret..
Thank you for your time!
You should use $s0 for the preserved value, instead of $t0.
You should be subtracting one from $a0 after copying from $s0 before the recursive call.
The multiply then would go between the $a0 return value and the $s0 preserved value.
The reason $s0 will work but $t0 won't (to preserve the original $a0 input), is that you are making the (correct) effort to save the s registers.
However, you are not restoring the saved values in function epilogue, nor cutting back the stack, nor reloading $ra...
Here's a recursive factorial function in RISC-V from my RV32I assembly programmer's quick reference: (comments welcome!)
.text # recursive implementation of factorial
.globl __start
fact: # arg: n in a0, returns n! in a1
addi sp, sp, -8 # reserve our stack area
sw ra, 0(sp) # save the return address
li t0, 2
blt a0, t0, ret_one # 0! and 1! == 1
sw a0, 4(sp) # save our n
addi a0, a0, -1
jal fact # call fact (n-1)
# a1 <- fact(n-1)
lw t0, 4(sp) # t0 <- n
mul a1, t0, a1 # a1 <- n * fact(n-1)
j done
ret_one:
li a1, 1
done:
lw ra, 0(sp) # restore return address from stack
addi sp, sp, 8 # free our stack frame
jr ra # and return
__start:
li a0, 5 # compute 5!
jal fact
li a0, 1 # print it
ecall
li a0, 17
ecall # and exit
To test it, my favorite lightweight RISC-V simulators are Venus and Jupiter.
I want to write code that has a public C interface, but is otherwise implemented entirely in MIPS, mainly as a learning exercise. However, I'm stuck fighting GAS because it seems to assume it knows better than I do.
To illustrate, let's say I want to implement the following in MIPS:
int bar(void)
{
return (4 / 2);
}
As I mentioned above, I want to be able to call ASM routines from C, so we'll need a C code file, bar.c, as well as the MIPS assembly in bar.S.
bar.c:
extern int bar(void);
void _start()
{
int foo = bar();
}
bar.S:
.global bar .text
bar:
addi $2, $0, 4
addi $3, $0, 2
div $2, $3
mflo $2
jr $31
addu $3, $0, $0
mipsel-none-elf-gcc bar.c bar.S -o bar.elf -ffreestanding -nostdinc -nostdlib successfully compiles this, but mipsel-none-elf-objdump -d bar.elf shows that GAS is messing with my bar() code:
00400050 <bar>:
400050: 20020004 addi v0,zero,4
400054: 20030002 addi v1,zero,2
400058: 14600002 bnez v1,400064 <bar+0x14>
40005c: 0043001a div zero,v0,v1
400060: 0007000d break 0x7
400064: 2401ffff li at,-1
400068: 14610004 bne v1,at,40007c <bar+0x2c>
40006c: 3c018000 lui at,0x8000
400070: 14410002 bne v0,at,40007c <bar+0x2c>
400074: 00000000 nop
400078: 0006000d break 0x6
40007c: 00001012 mflo v0
400080: 00001012 mflo v0
400084: 03e00008 jr ra
400088: 00000000 nop
40008c: 00001821 move v1,zero
I don't want the division checks, or delay slot fixups, or the assembler being "helpful" in any way here: I'm perfectly capable of handling those things myself if necessary. How do I tell GAS to just be a dumb assembler?
This one was solved in the comments. GAS accepts a 3-operand version of div where division checks are disabled if the first operand is $0. As for the delay slot, a .set noreorder directive prevents reordering of instructions (duh). Changing the assembly to:
.set noreorder
.global bar .text
bar:
addi $2, $0, 4
addi $3, $0, 2
div $0, $2, $3
mflo $2
jr $31
addu $3, $0, $0
produces the correct output:
00400050 <bar>:
400050: 20020004 addi v0,zero,4
400054: 20030002 addi v1,zero,2
400058: 0043001a div zero,v0,v1
40005c: 00001012 mflo v0
400060: 03e00008 jr ra
400064: 00001821 move v1,zero