Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Possible to inline this code?

3 views
Skip to first unread message

Jim Leonard

unread,
May 31, 2008, 1:33:25 PM5/31/08
to
I have a function that I am using in several places in an inner loop.
The function converts an input nybble to it's ascii equivalent:

Function HexNybble(b:byte):char; assembler;
{thanks to Norbert Juffa}
asm
mov al,b
cmp al,10 {if x<10, set CF=1}
sbb al,69h {0-9: 96h..9Fh, A-F: A1h..A6h}
das {0-9: subtr. 66h -> 30h-39h;
A-F: subtr. 60h -> 41h-46h}
end;

The problem is, this code is (obviously) being CALL'd every time it is
used, and each CALL (28 cycles) and RET (26 cycles) are adding 54
unnecessary cycles to this code.

Since this is in an inner loop, and since I'm using this function in
at least 200 places across modules and don't want to rewrite every
single section to a small asm block, I was hoping to convert it to an
inline() statement instead. I know from past experience (and the
manual) that inline() code is not CALLd but rather the compiler
"injects" the code directly into the binary where the "call" to that
code is made, like a macro.

I have only successfully converted procedures that don't take
variables to inline; the manual seems to state that variables can be
denoted with things like \ or < or something but I don't understand
the syntax. Is it possible to convert the above 4-line assembler
function into inline() code?

Jim Leonard

unread,
May 31, 2008, 7:06:04 PM5/31/08
to
On May 31, 12:33 pm, Jim Leonard <MobyGa...@gmail.com> wrote:
> I have a function that I am using in several places in an inner loop.
> The function converts an input nybble to it's ascii equivalent:
>
> Function HexNybble(b:byte):char; assembler;
> {thanks to Norbert Juffa}
> asm
> mov al,b
> cmp al,10 {if x<10, set CF=1}
> sbb al,69h {0-9: 96h..9Fh, A-F: A1h..A6h}
> das {0-9: subtr. 66h -> 30h-39h;
> A-F: subtr. 60h -> 41h-46h}
> end;

Never mind, I disassembled a test compile in Turbo Debugger and
figured out how to do it:

Function HexNybble(w:word):char;
inline(
$58/ {pop ax}
$3C/$0A/ {cmp al,0A}
$1C/$69/ {sbb al,69}
$2F {das}
);

Inline, it's still not as tight as I'd like it; there is a PUSH AX to
put the value onto the stack, then the POP AX immediately follows
it :-/ But it's better than the CALL and RET, which are 5-6 times
worse!

Times like this that I wish Turbo Pascal had a built-in macro facility.

Marco van de Voort

unread,
Jun 1, 2008, 5:33:26 AM6/1/08
to
On 2008-05-31, Jim Leonard <Moby...@gmail.com> wrote:
>
> Times like this that I wish Turbo Pascal had a built-in macro facility.

Wrong wish, too easy macro facilities lead to abuse.

Better have inlining of proper functions, also across units. Admitted it
doesn't solve this case (assembler can't be inlined, and you can see why
from the example), but it is a very powerful feature.

Dr J R Stockton

unread,
Jun 1, 2008, 4:54:38 PM6/1/08
to
In comp.lang.pascal.borland message <9ab68dd0-ca4a-417f-8e4c-a46560de5e0
7...@d77g2000hsb.googlegroups.com>, Sat, 31 May 2008 16:06:04, Jim Leonard
<Moby...@gmail.com> posted:

>
>Function HexNybble(w:word):char;
>inline(
> $58/ {pop ax}
> $3C/$0A/ {cmp al,0A}
> $1C/$69/ {sbb al,69}
> $2F {das}
>);
>
>Inline, it's still not as tight as I'd like it; there is a PUSH AX to
>put the value onto the stack, then the POP AX immediately follows
>it :-/ But it's better than the CALL and RET, which are 5-6 times
>worse!

See whether you can start with
function HexNybble(const w : word) : char ;
or function HexNybble(var w : word) : char ;

--
(c) John Stockton, nr London UK. ?@merlyn.demon.co.uk Turnpike v6.05 MIME.
<URL:http://www.merlyn.demon.co.uk/> TP/BP/Delphi/&c., FAQqy topics & links;
<URL:http://www.merlyn.demon.co.uk/clpb-faq.txt> RAH Prins : c.l.p.b mFAQ;
<URL:ftp://garbo.uwasa.fi/pc/link/tsfaqp.zip> Timo Salmi's Turbo Pascal FAQ.

Jim Leonard

unread,
Jun 2, 2008, 11:45:39 AM6/2/08
to
On Jun 1, 3:54 pm, Dr J R Stockton <j...@merlyn.demon.co.uk> wrote:
> >Inline, it's still not as tight as I'd like it; there is a PUSH AX to
> >put the value onto the stack, then the POP AX immediately follows
> >it :-/ But it's better than the CALL and RET, which are 5-6 times
> >worse!
>
> See whether you can start with
> function HexNybble(const w : word) : char ;
> or function HexNybble(var w : word) : char ;

Wouldn't (var w:word) just push the address of w onto the stack? That
would be larger and slower (I'd still have to POP, but instead of the
value I'd get a reference to the value). I'll check tonight.

Jim Leonard

unread,
Jun 2, 2008, 7:54:12 PM6/2/08
to
On Jun 2, 10:45 am, Jim Leonard <MobyGa...@gmail.com> wrote:
> > See whether you can start with
> > function HexNybble(const w : word) : char ;
> > or function HexNybble(var w : word) : char ;
>
> Wouldn't (var w:word) just push the address of w onto the stack? That
> would be larger and slower (I'd still have to POP, but instead of the
> value I'd get a reference to the value). I'll check tonight.

This turns out to be worse; the compiler generates the following for a
var inline:

mov di,(address of var)
push ds
push di

...and then the inline code. So this moves the address to the
variable into DI, pushes DS, and pushes DI. This is a lot more work
than just a non-var parameter, which pushes the variable itself.

Jim Leonard

unread,
Jun 2, 2008, 7:56:02 PM6/2/08
to
On Jun 1, 4:33 am, Marco van de Voort <mar...@stack.nl> wrote:
> > Times like this that I wish Turbo Pascal had a built-in macro facility.
>
> Wrong wish, too easy macro facilities lead to abuse.

Why? I *want* to abuse it :-) (I need the speed in a few places)

> Better have inlining of proper functions, also across units. Admitted it
> doesn't solve this case (assembler can't be inlined, and you can see why
> from the example), but it is a very powerful feature.

Well, a proper macro facility would eliminate the PUSH/POP behavior I
just noted, so yes, I'd rather have macros. With Inline code Pascal
still thinks I am writing a "normal" procedure/function...

Marco van de Voort

unread,
Jun 3, 2008, 4:08:24 AM6/3/08
to
On 2008-06-02, Jim Leonard <Moby...@gmail.com> wrote:
>>
>> Wrong wish, too easy macro facilities lead to abuse.
>
> Why? I *want* to abuse it :-) (I need the speed in a few places)

There is no difference in speed. Macro's are the "my only tool is a hammer,
so make it a big and blunt one". It has the perception of powerful, but it
damages the equipment too much long term.

>> Better have inlining of proper functions, also across units. Admitted it
>> doesn't solve this case (assembler can't be inlined, and you can see why
>> from the example), but it is a very powerful feature.
>
> Well, a proper macro facility would eliminate the PUSH/POP behavior I
> just noted, so yes, I'd rather have macros. With Inline code Pascal
> still thinks I am writing a "normal" procedure/function...

Not necessarily, since with inlining pascal that entire part (parameter
allocation, prologue, epilogue) is governed by the compiler, and thus can be
eliminated by it.

Jim Leonard

unread,
Jun 3, 2008, 11:11:37 AM6/3/08
to
On Jun 3, 3:08 am, Marco van de Voort <mar...@stack.nl> wrote:
> > Why? I *want* to abuse it :-) (I need the speed in a few places)
>
> There is no difference in speed. Macro's are the "my only tool is a hammer,
> so make it a big and blunt one". It has the perception of powerful, but it
> damages the equipment too much long term.

Then you'll have to explain why, since it seems like a benefit to me.

> >> Better have inlining of proper functions, also across units. Admitted it
> >> doesn't solve this case (assembler can't be inlined, and you can see why
> >> from the example), but it is a very powerful feature.
>
> > Well, a proper macro facility would eliminate the PUSH/POP behavior I
> > just noted, so yes, I'd rather have macros. With Inline code Pascal
> > still thinks I am writing a "normal" procedure/function...
>
> Not necessarily, since with inlining pascal that entire part (parameter
> allocation, prologue, epilogue) is governed by the compiler, and thus can be
> eliminated by it.

I'm thinking of macros in assembler; are you thinking of something
else? For clarification, what I wish I had in TP7 is the ability to
do something like this (imaginary syntax follows):

Function DoSomething(inw:word):word; macro;
asm
mov ax,inw
{do asm stuff here}
end;

...and then in the source I could use it like this:

{stuff #1}
newvar:=DoSomething(inw);
{stuff #2}

...and the compiled output would be something like:

{stuff #1}
mov ax,inw
{do asm stuff here}
mov newvar,ax {typical passing of function result to variable}
{stuff #2}

Why would that be "bad" or "abuse"? For very short functions/
procedures such as the "convert nybble value to ascii" function I
previously posted, it would be a great big help instead of having to
put inline assembler all over the source code.

Jason Burgon

unread,
Jun 4, 2008, 7:42:10 AM6/4/08
to
"Jim Leonard" <Moby...@gmail.com> wrote in message
news:033794a2-e3a9-40e0...@e53g2000hsa.googlegroups.com...

> On Jun 1, 3:54 pm, Dr J R Stockton <j...@merlyn.demon.co.uk> wrote:
> > >Inline, it's still not as tight as I'd like it; there is a PUSH AX to
> > >put the value onto the stack, then the POP AX immediately follows
> > >it :-/ But it's better than the CALL and RET, which are 5-6 times
> > >worse!
> >
> > See whether you can start with
> > function HexNybble(const w : word) : char ;
> > or function HexNybble(var w : word) : char ;
>
> Wouldn't (var w:word) just push the address of w onto the stack?

Yes.

> That would be larger and slower (I'd still have to POP, but instead of
> the value I'd get a reference to the value).

Indeed.

--
Jay

Jason Burgon - author of Graphic Vision
http://homepage.ntlworld.com/gvision


Wolfgang Ehrhardt

unread,
Jun 4, 2008, 12:27:04 PM6/4/08
to
On Sat, 31 May 2008 10:33:25 -0700 (PDT), Jim Leonard
<Moby...@gmail.com> wrote:

>I have a function that I am using in several places in an inner loop.
>The function converts an input nybble to it's ascii equivalent:
>
>Function HexNybble(b:byte):char; assembler;
>{thanks to Norbert Juffa}
>asm
> mov al,b
> cmp al,10 {if x<10, set CF=1}
> sbb al,69h {0-9: 96h..9Fh, A-F: A1h..A6h}
> das {0-9: subtr. 66h -> 30h-39h;
> A-F: subtr. 60h -> 41h-46h}
>end;
>
>The problem is, this code is (obviously) being CALL'd every time it is
>used, and each CALL (28 cycles) and RET (26 cycles) are adding 54
>unnecessary cycles to this code.

If you want real speed, setup a (global) lookup table

const Nibbles: array[byte] of char = '012345...'

Wolfgang
--
In order to e-mail me a reply to this message, you will have
to remove PLEASE.REMOVE from the address shown in the header
or get it from http://home.netsurf.de/wolfgang.ehrhardt
(Free open source Crypto, AES, CRC, Hash for Pascal/Delphi)

Jim Leonard

unread,
Jun 4, 2008, 5:52:57 PM6/4/08
to
On Jun 4, 11:27 am, Wolfgang.Ehrhardt.PLEASE.REM...@munich.netsurf.de

(Wolfgang Ehrhardt) wrote:
> If you want real speed, setup a (global) lookup table
>
> const Nibbles: array[byte] of char = '012345...'

Nope, that's both larger and slower than what I posted. It is
impossible to beat my code in size or speed for my target platform
(8088/8086)... although you're welcome to try :-)

Dr J R Stockton

unread,
Jun 4, 2008, 4:22:13 PM6/4/08
to
In comp.lang.pascal.borland message <4846c1ae...@news.individual.ne
t>, Wed, 4 Jun 2008 16:27:04, Wolfgang Ehrhardt <Wolfgang.Ehrhardt.PLEAS
E.RE...@munich.netsurf.de> posted:

>On Sat, 31 May 2008 10:33:25 -0700 (PDT), Jim Leonard
><Moby...@gmail.com> wrote:
>
>>I have a function that I am using in several places in an inner loop.
>>The function converts an input nybble to it's ascii equivalent:
>>
>>Function HexNybble(b:byte):char; assembler;
>>{thanks to Norbert Juffa}
>>asm
>> mov al,b
>> cmp al,10 {if x<10, set CF=1}
>> sbb al,69h {0-9: 96h..9Fh, A-F: A1h..A6h}
>> das {0-9: subtr. 66h -> 30h-39h;
>> A-F: subtr. 60h -> 41h-46h}
>>end;
>>
>>The problem is, this code is (obviously) being CALL'd every time it is
>>used, and each CALL (28 cycles) and RET (26 cycles) are adding 54
>>unnecessary cycles to this code.
>
>If you want real speed, setup a (global) lookup table
>
>const Nibbles: array[byte] of char = '012345...'

That will reduce the source code, at each point of use, to the same size
as the function non-call originally hoped for.

As the input is a nibble, only the first 16 elements of the array will
be used.

For speed, there should be no range-checking there. OTOH, I consider it
unwise to have range-checking off globally. If the array is indeed
byte-sized, and the indexes used are all byte-sized, then ISTM that the
compiler should feel no need to check their range.

Wolfgang Ehrhardt

unread,
Jun 5, 2008, 1:16:17 PM6/5/08
to

So why do you complain about missing speed if your code is the fastet
possible? :)

Assuming the byte to be converted already in al, your code with inline
will generate

push ax 11
pop ax 8
cmp al,10 4
sbb al,69h 4
das 4

with a total of 31 cycles on a 8086 (if I looked up correctly). The
global array code will generate

xor ah,ah 3
mov di,ax 2
mov al,[di+offset nibbles] 10

which is less than half of the inline code. Not to mention the
implicit error detection possibility if the input is greater than 15
(just set nibbles[i]:='?' for i>15).

Jim Leonard

unread,
Jun 6, 2008, 12:02:01 AM6/6/08
to
On Jun 5, 12:16 pm, Wolfgang.Ehrhardt.PLEASE.REM...@munich.netsurf.de

(Wolfgang Ehrhardt) wrote:
> >Nope, that's both larger and slower than what I posted. It is
> >impossible to beat my code in size or speed for my target platform
> >(8088/8086)... although you're welcome to try :-)
>
> So why do you complain about missing speed if your code is the fastet
> possible? :)

Because, in cases like this, I really do enjoy being proven wrong
(since it just means my program will get faster ;-) Doing some
research, I found that constant values (ie. something like
answer:=LookupTable[$a]) assemble to this:

mov al,[000C] ; or whatever the absolute offset is

I hadn't thought of this, so for constant parameters, this is clearly
the winner.

But what if the result is a variable, or passed from another
function? I wrote the following code to test this:

{$R-,G-,S-}

program NybASCII;

const
NybLookup:array[0..15] of char = '0123456789ABCDEF';
var
nybval:byte;
answer:char;

Function HexNybble(w:word):char;
inline(
$58/ {pop ax}
$3C/$0A/ {cmp al,0A}
$1C/$69/ {sbb al,69}
$2F {das}
);

begin
nybval:=$a;
answer:=HexNybble(nybval);
answer:=NybLookup[nybval];
end.

My solution, as previously noted, compiles to this (I am omitting the
setup code to get the value into AL in the first place):

50 push ax {11 cycles}
58 pop ax {08 cycles}
3C0A cmp al,0A {04 cycles}
1C69 sbb al,69 {04 cycles}
2F das {04 cycles}
sum of cycles: 31

Counting cycles, keeping in mind that each opcode byte also takes 4
cycles to read, the total comes to 59 cycles. Now, the lookup table
(also omitting the setup code to get the value into AL):

8BF8 mov di,ax {02 cycles}
8A85xxxx mov al,[di+xxxx] {08+09 cycles}

The total for this comes to 19 cycles. With opcode read times, total
is 43. So yes, this still wins if the compiler is generating it.
However, armed with this new information, I can still beat the
compiler:

BBxxxx mov bx,offset NybLookup {04 cycles}
D7 xlat {11 cycles}

Total cycles, including 4 cycles per opcode byte, is 31 -- I win! ;-)

Not really, of course, since this is a pain in the ass to insert in
the source code all over the place, so I'll probably just stick to the
array reference. If I truly truly need speed, I'll inline assembler
the entire thing using xlat.

Dr J R Stockton

unread,
Jun 6, 2008, 5:54:03 PM6/6/08
to
In comp.lang.pascal.borland message <f9a6bda2-8738-42bf-9dbb-27add29d9d5
a...@f36g2000hsa.googlegroups.com>, Thu, 5 Jun 2008 21:02:01, Jim Leonard
<Moby...@gmail.com> posted:

>
>Not really, of course, since this is a pain in the ass to insert in
>the source code all over the place, so I'll probably just stick to the
>array reference. If I truly truly need speed, I'll inline assembler
>the entire thing using xlat.

Have you considered the possibilities of using a Make File with a
scripted editor (or a Pascal program) as preprocessor? You might then
be able to write the source code using lines such as Hex ::= Nyb(wd)
and have them converted to asm..end blocks before the command-line
compiler sees them. And, if all those are in Units, you might still run
the Main from the IDE.

But ISTM that it's a rather peculiar program if a large amount of its
work is converting nibbles.

Jim Leonard

unread,
Jun 8, 2008, 1:02:54 PM6/8/08
to
On Jun 6, 4:54 pm, Dr J R Stockton <j...@merlyn.demon.co.uk> wrote:
> Have you considered the possibilities of using a Make File with a
> scripted editor (or a Pascal program) as preprocessor? You might then
> be able to write the source code using lines such as Hex ::= Nyb(wd)
> and have them converted to asm..end blocks before the command-line
> compiler sees them. And, if all those are in Units, you might still run
> the Main from the IDE.

I did actually consider that possibility (I was going to write it
myself, or at least test the idea with {$I} include files. But
there's only four loops in the entire program where it's a problem, so
I'll probably convert those loops to asm.

Robert Riebisch

unread,
Jun 8, 2008, 1:58:51 PM6/8/08
to
Jim Leonard wrote:

> I did actually consider that possibility (I was going to write it
> myself, or at least test the idea with {$I} include files. But
> there's only four loops in the entire program where it's a problem, so
> I'll probably convert those loops to asm.

You could also write your own tiny Pascal compiler. ;-) Open-source
community is still missing a 16-bit TP replacement.

--
Robert Riebisch
Bitte NUR in der Newsgroup antworten!
Please reply to the Newsgroup ONLY!

Marco van de Voort

unread,
Jun 8, 2008, 2:36:15 PM6/8/08
to
On 2008-06-08, Robert Riebisch <Robert....@arcor.de> wrote:
>> I did actually consider that possibility (I was going to write it
>> myself, or at least test the idea with {$I} include files. But
>> there's only four loops in the entire program where it's a problem, so
>> I'll probably convert those loops to asm.
>
> You could also write your own tiny Pascal compiler. ;-) Open-source
> community is still missing a 16-bit TP replacement.

There are lots of tiny compilers, but few 16-bit TP replacements. While TP
is fairly simple, it is also not THAT simple.

Robert Riebisch

unread,
Jun 8, 2008, 3:15:28 PM6/8/08
to
Marco van de Voort wrote:

> > You could also write your own tiny Pascal compiler. ;-) Open-source
> > community is still missing a 16-bit TP replacement.
>
> There are lots of tiny compilers, but few 16-bit TP replacements.

I know. :-/

> While TP is fairly simple, it is also not THAT simple.

Did I see that it would be easy? ;-)

Jim Leonard

unread,
Jun 9, 2008, 2:30:43 PM6/9/08
to
On Jun 8, 1:36 pm, Marco van de Voort <mar...@stack.nl> wrote:
> There are lots of tiny compilers, but few 16-bit TP replacements. While TP
> is fairly simple, it is also not THAT simple.

Agreed. Every time I use XLAT or some other 808x time/space-saving
special instruction, I keep thinking about why I never see that in
compiler output. Then I start thinking about developing a compiler,
and I suddenly understand why :-)

Robert Riebisch

unread,
Jun 9, 2008, 2:51:37 PM6/9/08
to
Jim Leonard wrote:

> Agreed. Every time I use XLAT or some other 808x time/space-saving
> special instruction, I keep thinking about why I never see that in
> compiler output. Then I start thinking about developing a compiler,
> and I suddenly understand why :-)

A nice compiler, if code size is very important, is C--, but this is not
Pascal, of course. You might also have a look at BASM and ML1
(BASIC-like) or XPL0 (Pascal-like). You can find all links at
<http://www.bttr-software.de/links/#asm>.

Marco van de Voort

unread,
Jun 10, 2008, 4:39:20 AM6/10/08
to
On 2008-06-09, Jim Leonard <Moby...@gmail.com> wrote:
> Agreed. Every time I use XLAT or some other 808x time/space-saving
> special instruction, I keep thinking about why I never see that in
> compiler output. Then I start thinking about developing a compiler,
> and I suddenly understand why :-)

Compilers prefer orthogonal instructions. This is probably why the 80386
grants many instructions to use any of the GPR registers.

Moreover, the average compiler is pretty bad in optimizing for a specific
CPU. Some extra instructions are used (like movc in 686+), and some minor
rearrangements of frequently used sequences is done, but that's it.

E.g. Delphi misses such a switch at all.

0 new messages