Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Approach/leap #206

Merged
merged 18 commits into from
Jan 15, 2024
Merged

Approach/leap #206

merged 18 commits into from
Jan 15, 2024

Conversation

clechasseur
Copy link
Sponsor Contributor

@clechasseur clechasseur commented Jan 14, 2024

Four approaches to the Leap exercise in MIPS assembly.

A couple of notes:

  • There isn't a lot of documentation for MIPS assembly so I just linked to the MIPS Green Sheet.
  • MIPS assembly syntax coloring is not available in GitHub markdown, so I used asm (which is probably the syntax coloring for x86_64 assembly). Close enough 😉

@keiravillekode
Copy link
Contributor

Thanks you for these.

The different approaches have a lot in common, in that they use similar instructions. The main difference is in how often they perform 1, 2 or 3 divisions.

Wider variation in approach is possible. We can merge as-is, and leave approaches using macro, rem, and and for later exercises.

Using macro

Examples
https://exercism.org/tracks/mips/exercises/leap/solutions/hyphenrf
https://exercism.org/tracks/mips/exercises/leap/solutions/ladokp

Using rem

Recent MIPS processors have the rem instruction.
rem Rdest, Rsrc1, Src2
Put the remainder from dividing the integer in register
Rsrc1 by the integer in Src2 into register Rdest.

Example
https://exercism.org/tracks/mips/exercises/leap/solutions/bgraf

Only perform one division, by 100

Use bitwise operations to check if we have a multiple of 4

Example
https://exercism.org/tracks/mips/exercises/leap/solutions/keiraville (my own)

@clechasseur
Copy link
Sponsor Contributor Author

Interesting! I didn't know any MIPS assembly until two days ago and it shows 😉

I have a few questions:

  1. Do you have a reference that mentions the rem instruction anywhere? I only had access to the reference cards mentioned in the track's documentation and I didn't see it.
  2. When looking at the solution that uses rem, I noticed they use it with an immediate value. This made be go back to my helper function approach and try something: it seems that I can use the sub instruction both with a register and an immediate value as its last parameter (which isn't mentioned in the reference cards I've looked at). How does that work? Does the instruction actually support both or does the interpreter use a different instruction under the hood when we use an immediate value?

Looking back, I think you're right about the fact that the approaches are actually pretty similar. In the approaches for other tracks, they usually mention approaches that differ in the use of functions or language constructs; in that sense, my approaches are not as interesting, whereas writing some using a macro, the rem instruction, etc. would make for a more diverse set of approaches.

I like your submissions too. The trick with using year & 3 to determine if it is divisible by 4 is pretty neat (and it could even be applied in other languages!). I also like your second iteration that uses arithmetic with the check results. Those, however, differ both in terms of the instructions used and the actual algorithm involved. I would consider them more advanced (IMHO).

All things considered, I think it might be a good idea to rework my submission:

  1. Drop the boolean-chain-reverse, boolean-chain-with-helper-fn approaches
  2. Rename the boolean-chain to something like using-div
  3. Add an approach named using-rem that uses the rem instruction (if we can find a reference to it somewhere)
  4. Optionally, add an approach named using-and that showcases your third iteration, replacing my bitwise approach

I would maybe skip the approach that uses add and slti (your second iteration).

What do you think? If you agree, I can update the PR.

@keiravillekode
Copy link
Contributor

keiravillekode commented Jan 14, 2024

Your suggestions all sound excellent.

Regarding converting % 4 to & 3:

  • in statically typed languages, I expect a good compiler will do that automatically.
  • in interpreters for dynamically typed languages, there are often so many other overheads, that it hardly matters
  • in assembly it matters the most.

Here are some references that mention rem
https://www.cs.tufts.edu/comp/140/lectures/Day_3/mips_summary.pdf
https://jarrettbillingsley.github.io/teaching/classes/cs0447/guides/instructions.html
https://pages.cs.wisc.edu/~markhill/cs354/Fall2008/notes/MAL.instructions.html

When performing addition/subtraction or bitwise operations with immediate values, the relevant instruction ends with an i:

  • addi
  • subi
  • andi
  • ...

The assembler can understand what we are doing, and turn an and into an andi, for example.

So the references above didn't even mention the i instructions, unlike the track references
https://inst.eecs.berkeley.edu/~cs61c/resources/MIPS_Green_Sheet.pdf
http://www.cburch.com/cs/330/reading/mips-ref.pdf

With regards to rem or remi, I wasn't sure, so I tested:

rem $t0, $a0, 4

is accepted by the assembler.

remi $t0, $a0, 4

is rejected.

"remi" is not a recognized operator

Is rem with an immediate a single instruction, or a pseudo-instruction that gets converted into two instructions? I don't know.

@clechasseur
Copy link
Sponsor Contributor Author

Thanks for the detailed explanation. I'll update the PR later today. With luck this can be merged before the beginning of #48in24.

(And sorry for not discussing this with you beforehand - it was kind of a "spur of the moment" thing, since Tuesday January 16th was coming soon. I'll try to think of it more in advance next time.)

Copy link
Contributor

@keiravillekode keiravillekode left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of thanks.


For more information, check the [approach using `rem`][approach-using-rem].

## Approach: Using the `andi` instruction
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This heading should be something like

"Using a \"divisible by\" `macro`"

Copy link
Sponsor Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤦 Looks like I was tired. 😅


```c
int is_leap_year(int year) {
return year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);

Copy link
Sponsor Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it, however in this case it is actually equivalent; but I think you are correct that it is easier to understand with proper grouping.


```c
int is_leap_year(int year) {
return year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);

@clechasseur
Copy link
Sponsor Contributor Author

@keiravillekode I've fixed the issues you pointed out. Thanks for the review!

@clechasseur
Copy link
Sponsor Contributor Author

Also of note: I noticed that I had left an empty "Which approach to use?" header, so I removed it. I don't really know if there's a "better" approach here; I don't have enough MIPS assembly experience unfortunately. But if you want I can add it back if there is something interesting to discuss.

@keiravillekode keiravillekode merged commit bfd063d into exercism:main Jan 15, 2024
4 checks passed
@keiravillekode
Copy link
Contributor

Also of note: I noticed that I had left an empty "Which approach to use?" header, so I removed it. I don't really know if there's a "better" approach here; I don't have enough MIPS assembly experience unfortunately. But if you want I can add it back if there is something interesting to discuss.

I don't think we need it

Size

We don't have measurements for the binary code size.

Speed

We haven't counted clock cycles for the different instructions, or taken measurements. I assume and is fastest.

Maintainability

Macros can have big advantages here.

@clechasseur clechasseur deleted the approach/leap branch January 15, 2024 21:11
@ErikSchierboom
Copy link
Member

The different approaches have a lot in common, in that they use similar instructions'

Looking at the approaches, the rem and andi approaches are almost identical, correct? In this case, it might be better to have one approach and then to have a small section that explains that another instruction could also be used to the same effect.

But great work!

@keiravillekode
Copy link
Contributor

We could replace either the div or rem solution with logic from my new iteration 4 - it is branchless, equivalent to

int is_leap_year(int year) {
    int is_multiple_4 = (year % 4 == 0);
    int is_multiple_100 = (year % 100 == 0);
    int is_multiple_400 = (year % 400 == 0);
    return is_multiple_4 - is_multiple_100 + is_multiple_400;
}

Perhaps this could be the div solution.

I don't think any solution in the video was branchless.

@ErikSchierboom
Copy link
Member

I don't think any solution in the video was branchless.

Nope. That would be a perfect approach!

@keiravillekode
Copy link
Contributor

I don't think any solution in the video was branchless.

Nope. That would be a perfect approach!

@ErikSchierboom I uploaded a PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants