News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests

Main Menu

Hello from an absolute beginner

Started by brazer, January 15, 2023, 04:40:09 AM

Previous topic - Next topic

jj2007


hutch--

 :biggrin:

> Just so long as you realize that assembly language is not, and probably will never be, a really viable development tool for commercial applications, mainly because of the portability issue

This would certainly be debated by many who have written assembler code for many years. Go back to QMODEM where the later ASM version ate the earlier C version alive.

Assembler in Windows x86 - 64 is not a hobbyists social club but a tool designed for performance and it has multiple targets, object modules for Microsoft C/C++ compilers, DLLs in both 32 and 64 bit and of course, 32 and 64 bit executable files.

While some confuse the old push/call style with how ASM is or should be written, since the start of Win32, MASM has been capable of using the full API set and any Microsoft compatible library and you can write sections of asm code that are relatively high level. Being able to write hack API and library code gets much of the junk out of the way so that development time can be spent where assembler has no peers, algorithm design and code efficiency.

Timo is right, well written C/C++ can produce high quality binary code and the better C compilers usually have assemblers to go with them for when finer tuning is required.

brazer

hutch--,
great, I'm now convinced there is another "true" reason for asm beyond just enjoying it, performance.

Quote from: jj2007 on January 19, 2023, 11:34:37 AM
There are occasionally cases where the C/C++ compiler is on par with assembly, but generally we are not happy if we can't beat the beast by at least a factor 2 - see The Lab for some examples :cool:
Factor of 2 vs compiler sounds cool, I wonder how is it measured, is it by counting CPU cycles, by counting instructions or something else? I suppose there is a tool or library for this?

Quote from: jj2007 on January 19, 2023, 11:34:37 AM
Attached my Windows GUI template - pure Masm32 SDK code, 93 lines, 7.5kB exe, with a menu, an edit control and a WM_PAINT handler. I think it's about time to install the Masm32 SDK ;-)
93 lines and 8KB in unbelievable given the features of the window, that's certainly much less than what I would need to do it in cpp, perhaps without OOP approach may be possible.

Question: I see you're using constructs, a switch case and while loop, is this specific to masm assembler? I suppose it's not possible with non MS assembler?

Sorry if this sounds noobish but if the above is true, I wonder, how is performance affected by using MS specific features compared to pure assembly? is there some drawback?



ASM Formatter
https://github.com/metablaster/ASM-Formatter

jj2007

Quote from: brazer on January 20, 2023, 09:48:05 AMhow is it measured, is it by counting CPU cycles, by counting instructions or something else? I suppose there is a tool or library for this?

Macros. Have a look at The Laboratory, or at \masm32\macros\timers.asm.

QuoteQuestion: I see you're using constructs, a switch case and while loop, is this specific to masm assembler? I suppose it's not possible with non MS assembler?

Sorry if this sounds noobish but if the above is true, I wonder, how is performance affected by using MS specific features compared to pure assembly? is there some drawback?

No drawback at all. These macros generate very fast very small code under the hood, see here.

NoCforMe

Quote from: jj2007 on January 20, 2023, 09:59:22 AM
Quote from: brazer on January 20, 2023, 09:48:05 AMQuestion: I see you're using constructs, a switch case and while loop, is this specific to masm assembler? I suppose it's not possible with non MS assembler?

Sorry if this sounds noobish but if the above is true, I wonder, how is performance affected by using MS specific features compared to pure assembly? is there some drawback?

No drawback at all. These macros generate very fast very small code under the hood, see here.

Welll, again, macros: I don't see how that could be any faster (or smaller, even) than what I do to in lieu of switch ... case ... case ... without the help of macros at all:


[in a window proc]

MOV EAX, uMsg
CMP EAX, WM_COMMAND
JE do_command
CMP EAX, WM_PAINT
JE do_paint
CMP EAX, WM_CTLCOLORBTN
JE do_btncolor
...

do_command:
[handle commands]

do_paint:
[paint your window]

do_btncolor:

... etc.


Now, I don't know exactly what all those macros do, in terms of the code they generate, but it cannot be any simpler than what I just posted, and I suspect it's more complex, with jumps around blocks of code. So again, use macros if you think it makes your code more readable (doesn't do anything for me in that department), but don't go thinking it's better than just raw assembler coding.
Assembly language programming should be fun. That's why I do it.

hutch--

David,

   MOV   EAX, uMsg
   CMP   EAX, WM_COMMAND
   JE   do_command
   CMP   EAX, WM_PAINT
   JE   do_paint
   CMP   EAX, WM_CTLCOLORBTN
   JE   do_btncolor

You are doing a redundant move for the stack message arg. A message is an equate to an immediate value so you can do a direct comparison between uMsg and the message equate.

    cmp uMsg, WM_COMMAND

The technique you are suggesting is an old Turbo Assembler technique.

Have a look in "example02\tstyle" at the two examples of TASM style code.



NoCforMe

What the heck are you talking about? I'm saving memory references, Hutch, by putting the message into a register (EAX) and comparing that instead of repeated memory comparisons (uMsg). Isn't that what you speed-obsessed coders always prefer doing?

Compare my way

CPU Disasm
Address   Hex dump          Command                                  Comments
00401381  8B45 0C        MOV EAX,DWORD PTR SS:[ARG.2]
00401384  3D 11010000    CMP EAX,111
00401389  74 0A          JE SHORT ASPIdiag.00401395
0040138B  83F8 10        CMP EAX,10
0040138E  74 13          JE SHORT ASPIdiag.004013A3
00401390  83F8 01        CMP EAX,1
00401393  74 18          JE SHORT ASPIdiag.004013AD


to your way:

CPU Disasm
Address   Hex dump          Command                                  Comments
0040136C  817D 0C 110100 CMP DWORD PTR SS:[ARG.2],111
00401373  74 20          JE SHORT ASPIdiag.00401395
00401375  837D 0C 10     CMP DWORD PTR SS:[ARG.2],10
00401379  74 28          JE SHORT ASPIdiag.004013A3
0040137B  837D 0C 01     CMP DWORD PTR SS:[ARG.2],1
0040137F  74 2C          JE SHORT ASPIdiag.004013AD


Your way has n memory references; mine only has 1. Smaller and (not that it really matters) faster.

If I'm only handling one or two messages then sure, I'll just do


CMP uMsg, WM_COMMAND
JE do_command
Assembly language programming should be fun. That's why I do it.

NoCforMe

Sorry, I can barely make head or tails out of your English. Hard to tell what you're trying to say.

However, one thing you wrote:
Quote
go up write variables,go down write code

I know exactly what you're talking about there. It's probably the biggest drawback to my style of coding (no macros), which means that data definitions go up above under .data, and code lower down under .code. Meaning that you have to constantly switch between sections, unless you can remember all those damned names! So def. an advantage to use macros that let you define strings in the macro, for instance (or use C, which does the same thing). Still, I'll stick with my style for now.
Assembly language programming should be fun. That's why I do it.

hutch--

David,

Its the usual distinction,

1. Can you clock the difference ? I doubt that.
2. Do you need to use a register in a sequential compare which is slow at best ?
3. Is it vaguely even speed related code ?

While there is nothing wrong with your code and it will work correctly, I will make you the point that it is an ancient style that yields no advantage.

I in fact have never used a TASM style dispatcher, I wrote those 2 examples over 20 years ago to show bad code design.

My own style is a switch block, clean, fast and easy to read.

Straight out of the help file.

switch variable
  case value1 ; there must be at least one (1) case statement
    ; do something here
  case value2 ; optional extra case statement(s)
    ; do something else
  default ; optional default processing
    ; do any default processing
endsw

The thing that will turn into a nightmare for you is multi-level nesting. A switch block or an if block can be nested at multiple depth levels in a clean and clear manner (indenting helps here).


jj2007

Quote from: NoCforMe on January 20, 2023, 03:39:52 PMWelll, again, macros: I don't see how that could be any faster (or smaller, even) than what I do to in lieu of switch ... case ... case ... without the help of macros at all:


[in a window proc]

MOV EAX, uMsg
CMP EAX, WM_COMMAND
JE do_command
CMP EAX, WM_PAINT
JE do_paint
CMP EAX, WM_CTLCOLORBTN
JE do_btncolor
...

do_command:
[handle commands]

do_paint:
[paint your window]

do_btncolor:

... etc.


Now, I don't know exactly what all those macros do, in terms of the code they generate, but it cannot be any simpler than what I just posted, and I suspect it's more complex

There are a few cases where a switch-style macro produces more efficient code under the hood, but in general they produce exactly the code that a good coder would produce manually.

It's all about readability and maintainability. Your code is fine if the whole source is a few hundred lines long, and you have plenty of spare time. I have several sources well beyond 10,000 lines. I would go mad if I had to use jne stuff all over the place.

There is a reason why all (?) high level languages have introduced switch, select, repeat...until & friends.
There is no reason why an assembler programmer should not use them.

Except that once in his career, as a n00b, he should study what they do under the hood.

The same is true, obviously, for handy things such as
print "Hello World"

I sincerely hope, for your mental sanity, that you don't use StdOut and .data definitions for writing hello world :cool:

NoCforMe

Quote from: jj2007 on January 20, 2023, 07:24:08 PM
The same is true, obviously, for handy things such as
print "Hello World"

I sincerely hope, for your mental sanity, that you don't use StdOut and .data definitions for writing hello world :cool:

StdOut, that's for console programs, right? Don't write many of those, and when I do I use WriteConsole() for output.

I do put my data to be "printed" (funny how we still use that BASIC verb that doesn't mean what it says) in .data, instead of the sexy way you guys put it as you illustrated. Yes, I know it's "old school" and to be honest, a bit more of a pain in the ass (as I explained to--whoops, looks like that reply got deleted: ???). But you know what? I prefer it that way. I like to have all my data organized in one place, rather than spread out all over my code, which, BTW, often exceeds several thousand lines of code, so we're not talking itsy-bitsy programs here. To me, it's manageable, yes, even without macros and embedded strings and all that stuff.
Assembly language programming should be fun. That's why I do it.

jj2007

> I do put my data to be "printed" (funny how we still use that BASIC verb that doesn't mean what it says)

Use "cout" instead, much clearer :thumbsup:


Three options, side by side:

include \masm32\MasmBasic\MasmBasic.inc
.code
txHelloData     db "Hello data", 0
  Init
  int 3
  print offset txHelloData, 13, 10
  print offset txHelloData, 13, 10
  print "Hello World", 13, 10
  print "Hello World", 13, 10
  Print "Hello David", CrLf$
  Print "Hello David", CrLf$
EndOfCode


Under the hood:
Address   Hex dump          Command                                  Comments
004010BC  |.  CC            int3
004010BD  |.  68 24104000   push txHelloData                         ; /Arg1 = ASCII "Hello data"
004010C2  |.  E8 8D000000   call StdOut                              ; \SkelMbEmpty.StdOut
004010C7  |.  68 57804000   push offset ??00EA                       ; /Arg1 = ASCII "
"
004010CC  |.  E8 83000000   call StdOut                              ; \SkelMbEmpty.StdOut
004010D1  |.  68 24104000   push txHelloData                         ; /Arg1 = ASCII "Hello data"
004010D6  |.  E8 79000000   call StdOut                              ; \SkelMbEmpty.StdOut
004010DB  |.  68 5C804000   push offset ??00F1                       ; /Arg1 = ASCII "
"
004010E0  |.  E8 6F000000   call StdOut                              ; \SkelMbEmpty.StdOut
004010E5  |.  68 60804000   push offset ??00F2                       ; /Arg1 = ASCII "Hello World"
004010EA  |.  E8 65000000   call StdOut                              ; \SkelMbEmpty.StdOut
004010EF  |.  68 6C804000   push offset ??00F8                       ; /Arg1 = ASCII "
"
004010F4  |.  E8 5B000000   call StdOut                              ; \SkelMbEmpty.StdOut
004010F9  |.  68 70804000   push offset ??00F9                       ; /Arg1 = ASCII "Hello World"
004010FE  |.  E8 51000000   call StdOut                              ; \SkelMbEmpty.StdOut
00401103  |.  68 7C804000   push offset ??00FF                       ; /Arg1 = ASCII "
"
00401108  |.  E8 47000000   call StdOut                              ; \SkelMbEmpty.StdOut
0040110D  |.  68 80804000   push offset ra_lbl2                      ; ASCII "Hello David"
00401112  |.  6A 01         push 1                                   ; /Arg2 = 1
00401114  |.  6A 02         push 2                                   ; |Arg1 = 2
00401116  |.  E8 DE130000   call MbPrint                             ; \SkelMbEmpty.MbPrint
0040111B  |.  68 80804000   push offset ra_lbl2                      ; ASCII "Hello David"
00401120  |.  6A 01         push 1                                   ; /Arg2 = 1
00401122  |.  6A 02         push 2                                   ; |Arg1 = 2
00401124  |.  E8 D0130000   call MbPrint                             ; \SkelMbEmpty.MbPrint


Option 1 (Masm32 SDK, old style):
- 20 bytes per call
- reuses txHelloData (15 bytes extra once)
- you can't see the string, it's declared far away, so you will have to add a comment to understand your own code

Option 2 (Masm32 SDK, modern style):
- 20 bytes per call
- creates a new "Hello World" and a new CrLf for each call, i.e. 15 bytes extra per call
- you can see the string right in your call

Option 3 (MasmBasic):
- 14 bytes per call (Arg3 -> ra_lbl2, /Arg2 = 1 -> push CrLf, /Arg1 = 2 -> 2 args)
- reuses "Hello David" alias offset ra_lbl2
- you can see the string right in your call

For the real bare metal fans, there is option 4, a dozen lines for each "hello world" (\Masm32\m32lib\stdout.asm):
StdOut proc lpszText:DWORD

    LOCAL hOutPut  :DWORD
    LOCAL bWritten :DWORD
    LOCAL sl       :DWORD

    invoke GetStdHandle,STD_OUTPUT_HANDLE
    mov hOutPut, eax

    invoke StrLen,lpszText
    mov sl, eax

    invoke WriteFile,hOutPut,lpszText,sl,ADDR bWritten,NULL

    mov eax, bWritten
    ret

StdOut endp


(Oops, StrLen is a Masm32 library function, is that allowed in bare metal code?)

Note the attached executable will throw an exception because of the int 3. In OllyDbg, go to Options/Options/Just-in-time/Set OllyDbg to make Olly take over when the crash happens.

brazer

Quote from: daydreamer on January 20, 2023, 11:18:26 PM
for example messagebox call with chr$,ustr$ in one long line is my preferred coding style vs in editor scroll up to write in .data section for strings+other variables,scrolll down to write in .code is * many times

I suppose an editor with a "split window" feature won't change your mind? :icon_idea:
ASM Formatter
https://github.com/metablaster/ASM-Formatter

hutch--

 :biggrin:

The basic distinction I support is between "hack code" that gets things on the screen, interacts with the OS, handles file IO and all the rest of the "hacky things, versus => algorithm design ! The latter is where ASM shines, where it has the adjustment level that few others have and where it has the real speed when it matters.

Aspiring to the fastest MessageBox on the planet delivers results that go over with all the fanfare of a gnat breaking wind.

    push MB_OK
    push pTitle
    push pMessage
    push hWnd
    call MessageBoxA

Now this may be fine in 32 bit STDCALL but wait until you try it in 64 bit. A new world order, entirely different stack design, first 4 arg as registers, the old stuff does not work any longer.

jj2007

Quote from: hutch-- on January 21, 2023, 06:37:48 AMthe old stuff does not work any longer.

However, MsgBox 0, "A text", "A title", MB_OK works fine in both 64- and 32-bit land. That is the power of macros.