4 ms·
As someone coming from a C++/Rust background and dabbling in retro game assembly, the biggest roadblocks I've faced trying to understand hand-written SPC700 ass
by nyanpasu64 5y ago
As someone coming from a C++/Rust background and dabbling in retro game assembly, the biggest roadblocks I've faced trying to understand hand-written SPC700 assembly are flags, memorizing/looking up instructions, branches and jumps being "backwards" from C++ in x86-64 and a total wildcard in SPC700, unstructured control flow, and integer literals being dereferences rather than immediates by default. I haven't tried using HLA yet, but I get the impression its syntax would greatly assist with unstructured control flow (and possibly distinguishing between pointers/offsets and immediates). The manual at https://www.plantation-productions.com/Webster/HighLevelAsm/HLADoc/HLARef/HLARef_html/HLAReference.htm#50401281_pgfId-999075 https://www.plantation-productions.com/Webster/HighLevelAsm/... is relatable:
> For example, one might test the carry flag after an addition to determine if an unsigned overflow has occurred using code like the following:
add eax, 5
jnc NoOverflow
<< code to execute if overflow occurs >>
NoOverflow:
> Although this code is straightforward, you would be surprised how many students cannot visualize this code. On the other hand, if you feed them some pseudo code like:
add eax, 5
if( the carry flag is set ) then
<< code to execute if overflow occurs >>
endif
> Those same students won't have any problems understanding this code.
I too find reading assembly a hindrance to understanding the intent and behavior of the code, and I mentally map assembly to high-level behaviors similar to structured programming. If HLA is more accessible and productive for regular programmers as a tool to read/write reasonably-efficient code on architectures without good C compilers, I don't consider that repugnant but empowering. (Admittedly it's more disconnected from the output binary instructions than regular assembly.)
(Sidenote: are assemblers designed around simplicity of parsing and speed at processing compiler-produced assembly, or user experience for humans writing assembly, or is the latter more common on architectures without good C compilers?)
- shadowofneptune 5y ago> are assemblers designed around simplicity of parsing and speed at processing compiler-produced assembly, or user experience for humans writing assembly, or is the latter more common on architectures without good C compilers? That really depends on the assembler. Each one is different. Some support a degree of structured programming, while others do not even allow for relocatable addresses.
- WantonQuantum 5y agoAssembly language is usually an almost one-to-one correspondence with machine code instructions of the target architecture. Assemblers can build on that by providing macros, etc but it's fundamentally about the programmer being aware of the instructions the CPU supports and using them to program. I'm having trouble understanding the difficulty in the example. If "jnc" doesn't suit then there's "jc" to do the opposite. If the difficulty is the conceptual leap to unstructured branching then I'd say that's what the teacher's job is - teach the students how to think about it.
- chillfox 5y agoCryptic names like "jnc" and "jc" is the huge hindrance in understanding it. It would be so much easier if it was just called what it did instead of an acronym. "jump_if_condition_is_met" or "jump_short_if_not_carry" (or which ever one it maps to) is much better and clearer than "jnc"
- akira2501 5y agoI added brace to branch conversion in an MSP430 assembler I wrote. You could write code like: add eax, 5 jnc { nop } And it would automatically create the labels for you, syntactically, it was equivalent to what you've written above, but with labels like ".auto_brace_X". You could also do the typical "if-else" branching with slightly extended syntax like: add eax, 5 jnc { <carry is set> }:jmp { <carry is not set> } <continue> Which allowed easy extension to "do-while" loops: { <some loop> dec ecx }:jnz The trickiest part is remembering that all branches are "unless" branches instead of "if" branches, and it obviously only works for simple constructs and can make refactoring and optimization a pain; however, there was plenty of code that benefited from using this.
- pwdisswordfish9 5y agoAmazing that MSP430 has the same mnemonics as x86.
- akira2501 5y agolol.. was just trying to be familiar. here's some old code I dug up that used it: ; find next task in circular list Next(6 cycles) Top(14 cycles) { add.w #10, r10 ;2 increment pointer to next task area cmp.w #mms_task.end, r10 ;2 are we at the end of the task list? jnz { ;2 no? start next task .init: eint ;1 ensure interrupts are enabled bis.w #MMS_EACH, &mms_task.state ;5 ensure EACH state flag is set mov.w #mms_task.list, r10 ;2 restart at top of list } ; build state and check against task Skip(12 cycles) Take(31 cycles) mov.b &IFG2, r8 ;3 r8 = ifg2 interrupt flags and.w #(MMS_TX+MMS_RX), r8 ;1 get only TX and RX ready flags mov.w &mms_task.state, r9 ;3 r9 = current task state bis.w r8, r9 ;1 add TX and TX flags to global state ; determine if this task needs to run bit.w @r10, r9 ;2 task request flags & system flags }:jz ;2 no matches? find next task ; load the task state and return into it mov.w r10, &mms_task.ptr ;4 set current task area to r10 mov.w @r10+, r12 ;2 r12 = task[0] (request flags) mov.w @r10+, r8 ;2 r8 = task[1] (PC) mov.w @r10+, r4 ;2 r4 = task[2] mov.w @r10+, r5 ;2 r5 = task[3] mov.b @r10+, r6 ;2 r6 = task[4] (low byte) mov.b @r10, r7 ;2 r7 = task[4] (high byte) mov.w #0, r10 ;1 clear r10 pointer for safety mov.w r8, PC ;2 "return" into the yield