home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / crypl200.zip / BNLIB / LBN8086.ASM < prev    next >
Assembly Source File  |  1996-05-16  |  29KB  |  1,039 lines

  1. ;;; Assembly primitives for bignum library, 80x86 family.
  2. ;;;
  3. ;;; Copyright (c) 1995, Colin Plumb.
  4. ;;; For licensing and other legal details, see the file legal.c.
  5. ;;;
  6. ;;; Several primitives are included here.  Only lbnMulAdd1 is *really*
  7. ;;; critical, but once that's written, lnmMul1 and lbnSub1 are quite
  8. ;;; easy to write as well, so they are included here as well.
  9. ;;; lbnDiv21 and lbnModQ are so easy to write that they're included, too.
  10. ;;;
  11. ;;; All functions here are for large code, large data.
  12. ;;; All use standard "cdecl" calling convention: arguments pushed on the
  13. ;;; stack (ss:sp) right to left (the leftmost agrument at the lowest address)
  14. ;;; and popped by the caller, return values in ax or dx:ax, and register
  15. ;;; usage as follows:
  16. ;;;
  17. ;;; Callee-save (preserved by callee if needed):
  18. ;;;    ss, esp, cs, eip, ds, esi, edi, ebp, high byte of FLAGS except DF,
  19. ;;;    all other registers (CRx, DRx, TRx, IDT, GDT, LDT, TR, etc.).
  20. ;;; Caller-save (may be corrupted by callee):
  21. ;;;    es, eax, ebx, ecx, edx, low byte of flags (SF, ZF, AF, PF, CF)
  22. ;;;
  23. ;;; The direction flag (DF) is either preserved or cleared.
  24. ;;; I'm not sure what the calling convention is for fs and gs.  This
  25. ;;; code never alters them.
  26.  
  27. ;; Not all of this code has to be '386 code, but STUPID FUCKING MASM (5.0)
  28. ;; gives an error if you change in the middle of a segment.  Rather than
  29. ;; fight the thing, just enable '386 instructions everywhere.  (And lose
  30. ;; the error checking.)
  31. .386
  32.  
  33. _TEXT   segment para public use16 'CODE'    ; 16-byte aligned because '486 cares
  34.     assume  cs:_TEXT
  35.  
  36.     public  _lbnMulN1_16
  37.     public  _lbnMulAdd1_16
  38.     public  _lbnMulSub1_16
  39.     public    _lbnDiv21_16
  40.     public    _lbnModQ_16
  41.  
  42.     public  _lbnMulN1_32
  43.     public  _lbnMulAdd1_32
  44.     public  _lbnMulSub1_32
  45.     public    _lbnDiv21_32
  46.     public    _lbnModQ_32
  47.  
  48.     public    _not386
  49.  
  50.  
  51. ;; Prototype:
  52. ;; BNWORD16
  53. ;; lbnMulAdd_16(BNWORD16 *out, BNWORD16 *in, unsigned len, BNWORD16 k)
  54. ;;
  55. ;; Multiply len words of "in" by k and add to len words of "out";
  56. ;; return the len+1st word of carry.  All pointers are to the least-
  57. ;; significant ends of the appropriate arrays.  len is guaraneed > 0.
  58. ;;
  59. ;; This 16-bit code is optimized for an 8086/80286.  It will not be run
  60. ;; on 32-bit processors except for debugging during development.
  61. ;;
  62. ;; NOTE that it may be possible to assume that the direction flag is clear
  63. ;; on entry; this would avoid the need for the cld instructions.  Hoewever,
  64. ;; the Microsoft C libraries require that the direction flag be clear.
  65. ;; Thus, lbnModQ_16 clears it before returning.
  66. ;;
  67. ;; Stack frame:
  68. ;; +--------+ bp+18
  69. ;; |   k    |
  70. ;; +--------+ bp+16
  71. ;; |  len   |
  72. ;; +--------+ bp+14
  73. ;; |        |
  74. ;; +-  in  -+
  75. ;; |        |
  76. ;; +--------+ bp+10
  77. ;; |        |
  78. ;; +- out  -+
  79. ;; |        |
  80. ;; +--------+ bp+6
  81. ;; |        |
  82. ;; +-return-+
  83. ;; |        |
  84. ;; +--------+ bp+2
  85. ;; | old bp |
  86. ;; +--------+ bp
  87. ;;
  88. ;; Register usage for lbnMul1_16:
  89. ;; ds:[si]    in
  90. ;; es:[di]    out
  91. ;; bp        k
  92. ;; cx        loop counter (len/4)
  93. ;; dx,ax    high,low parts of product
  94. ;; bx        carry from previous multiply iteration
  95. ;;
  96. ;; Register usage for lbnMulAdd1_16 and lbnMulSub1_16:
  97. ;; ds:[si]    in
  98. ;; es:[bx+si]    out
  99. ;; bp        k
  100. ;; cx        loop counter (len/4)
  101. ;; dx,ax    high,low parts of product
  102. ;; di        carry from previous multiply iteration
  103. ;;
  104. ;; The reson for the difference is that straight mul can use stosw, but
  105. ;; the multiply and add or multiply and subtract add the result in, so
  106. ;; they have to reference es:[di] to add it in.
  107. ;;
  108. ;; The options are either "add ax,es:[di]; stosw" or "add es:[di],ax;
  109. ;; add di,2"; both take 10 cycles on an 80286, 27 on an 8086 and 35 on
  110. ;; an 8088 although the former is preferred since it's one byte smaller.
  111. ;; However, using [bx+si] is even faster; "add es:[bx+si],ax" takes
  112. ;; 7 cycles on an 80286, 25 on an 8086 and 33 on an 8088, as well as
  113. ;; being the smallest.  (Of course, stosw, at 3 on an 80286, 11 on an
  114. ;; 8086 amd 15 on an 8088 wins easily in the straight multiply case over
  115. ;; mov es:[bx+si],ax, which takes 3/18/22 cycles and is larger to boot.)
  116. ;;
  117. ;; Most of these register assignments are driven by the 8086's instruction
  118. ;; set.  The only really practical variation would be to put the multiplier
  119. ;; k into bx or di and use bp for carry, but if someone can make a faster
  120. ;; Duff's device using a lookup table, bx and di are useful because indexing
  121. ;; off them is more flexible than bp.
  122. ;;
  123. ;; Overview of code:
  124. ;;
  125. ;; len is guaranteed to be at least 1, so do the first multiply (with no
  126. ;; carry in) unconditionally.  Then go to a min loop unrolled 4 times,
  127. ;; jumping into the middle using a variant of Duff's device.
  128. ;;
  129. ;; The loop is constructed using the loop instruction, which does
  130. ;; "} while (--cnt)".  This means that we have to divide the count
  131. ;; by 4, and increment it so it doesn't start at 0.  To gain a little
  132. ;; bit more efficiency, we actually increment the count by 2, so the
  133. ;; minimum possible value is 3, which will be shifted down to produce 0.
  134. ;; usually in Duff's device, if the number of iterations is a multiple
  135. ;; of the unrolling factor, you branch to just before the loop conditional
  136. ;; and let it handle the case of 0.  Here, we have a special test for 0
  137. ;; at the head of the loop and fall through into the top of the loop
  138. ;; if it passes.
  139. ;;
  140. ;; Basically, with STEP being a multiply step, it's:
  141. ;;
  142. ;;     STEP;
  143. ;;    count += 2;
  144. ;;    mod4 = count % 4;
  145. ;;    count /= 4;
  146. ;;    switch(mod4) {
  147. ;;      case 3:
  148. ;;        if (count) {
  149. ;;              do {
  150. ;;                STEP;
  151. ;;      case 2:
  152. ;;                STEP;
  153. ;;      case 1:
  154. ;;                STEP;
  155. ;;      case 0:
  156. ;;                STEP;
  157. ;;            } while (--count);
  158. ;;        }
  159. ;;    }
  160. ;;
  161. ;; The switch() is actually done by two levels of branch instructions
  162. ;; rather than a lookup table.
  163.  
  164. _lbnMulN1_16    proc    far
  165.  
  166.     push    bp
  167.     mov    bp,sp
  168.     push    ds
  169.     push    si
  170.     push    di
  171.     cld
  172.  
  173.     les    di,[bp+6]    ; out
  174.     lds    si,[bp+10]    ; in
  175.     mov    cx,[bp+14]    ; len
  176.     mov     bp,[bp+16]    ; k
  177.  
  178. ;; First multiply step has no carry in
  179.     lodsw
  180.     mul    bp
  181.     stosw
  182.  
  183. ;; The switch() for Duff's device starts here
  184. ;; Note: this *is* faster than a jump table for an 8086 and '286.
  185. ;; 8086:  jump table: 44 cycles; this: 27/29/31/41
  186. ;; 80286: jump table: 25 cycles; this: 17/17/20/22
  187.     shr    cx,1
  188.     jc    SHORT m16_odd
  189.  
  190.     inc    cx
  191.     shr    cx,1
  192.     jc    SHORT m16_case2
  193.     jmp    SHORT m16_case0
  194.  
  195.     nop            ; To align loop
  196. m16_odd:
  197.     inc    cx
  198.     shr    cx,1
  199.     jnc    SHORT m16_case1
  200.     jz    SHORT m16_done    ; Avoid entire loop in this case
  201.  
  202. m16_loop:
  203.     lodsw
  204.     mov    bx,dx        ; Remember carry for later
  205.     mul    bp
  206.     add    ax,bx        ; Add carry in from previous word
  207.     adc    dx,0
  208.     stosw
  209. m16_case2:
  210.     lodsw
  211.     mov    bx,dx        ; Remember carry for later
  212.     mul    bp
  213.     add    ax,bx        ; Add carry in from previous word
  214.     adc    dx,0
  215.     stosw
  216. m16_case1:
  217.     lodsw
  218.     mov    bx,dx        ; Remember carry for later
  219.     mul    bp
  220.     add    ax,bx        ; Add carry in from previous word
  221.     adc    dx,0
  222.     stosw
  223. m16_case0:
  224.     lodsw
  225.     mov    bx,dx        ; Remember carry for later
  226.     mul    bp
  227.     add    ax,bx        ; Add carry in from previous word
  228.     adc    dx,0
  229.     stosw
  230.  
  231.     loop    m16_loop
  232.  
  233. m16_done:
  234.     mov    ax,dx
  235.     stosw            ; Store last word
  236.     pop    di
  237.     pop    si
  238.     pop    ds
  239.     pop    bp
  240.     ret
  241.  
  242. _lbnMulN1_16    endp
  243.  
  244.  
  245.     align    2
  246. _lbnMulAdd1_16    proc    far
  247.  
  248.     push    bp
  249.     mov    bp,sp
  250.     push    ds
  251.     push    si
  252.     push    di
  253.     cld
  254.  
  255.     les    bx,[bp+6]    ; out
  256.     lds    si,[bp+10]    ; in
  257.     mov    cx,[bp+14]    ; len
  258.     mov     bp,[bp+16]    ; k
  259.  
  260. ;; First multiply step has no carry in
  261.     lodsw
  262.     mul    bp
  263.     add    es:[bx],ax    ; This time, store in [bx] directly
  264.     adc    dx,0
  265.     sub    bx,si        ; Prepare to use [bx+si].
  266.  
  267. ;; The switch() for Duff's device starts here
  268. ;; Note: this *is* faster than a jump table for an 8086 and '286.
  269. ;; 8086:  jump table: 44 cycles; this: 27/29/31/41
  270. ;; 80286: jump table: 25 cycles; this: 17/17/20/22
  271.     shr    cx,1
  272.     jc    SHORT ma16_odd
  273.  
  274.     inc    cx
  275.     shr    cx,1
  276.     jc    SHORT ma16_case2
  277.     jmp    SHORT ma16_case0
  278.  
  279. ma16_odd:
  280.     inc    cx
  281.     shr    cx,1
  282.     jnc    SHORT ma16_case1
  283.     jz    SHORT ma16_done    ; Avoid entire loop in this case
  284.  
  285. ma16_loop:
  286.     lodsw
  287.     mov    di,dx        ; Remember carry for later
  288.     mul    bp
  289.     add    ax,di        ; Add carry in from previous word
  290.     adc    dx,0
  291.     add    es:[bx+si],ax
  292.     adc    dx,0
  293. ma16_case2:
  294.     lodsw
  295.     mov    di,dx        ; Remember carry for later
  296.     mul    bp
  297.     add    ax,di        ; Add carry in from previous word
  298.     adc    dx,0
  299.     add    es:[bx+si],ax
  300.     adc    dx,0
  301. ma16_case1:
  302.     lodsw
  303.     mov    di,dx        ; Remember carry for later
  304.     mul    bp
  305.     add    ax,di        ; Add carry in from previous word
  306.     adc    dx,0
  307.     add    es:[bx+si],ax
  308.     adc    dx,0
  309. ma16_case0:
  310.     lodsw
  311.     mov    di,dx        ; Remember carry for later
  312.     mul    bp
  313.     add    ax,di        ; Add carry in from previous word
  314.     adc    dx,0
  315.     add    es:[bx+si],ax
  316.     adc    dx,0
  317.  
  318.     loop    ma16_loop
  319.  
  320. ma16_done:
  321.     mov    ax,dx
  322.     pop    di
  323.     pop    si
  324.     pop    ds
  325.     pop    bp
  326.     ret
  327.  
  328. _lbnMulAdd1_16    endp
  329.  
  330.     align    2
  331. _lbnMulSub1_16    proc    far
  332.  
  333.     push    bp
  334.     mov    bp,sp
  335.     push    ds
  336.     push    si
  337.     push    di
  338.     cld
  339.  
  340.     les    bx,[bp+6]    ; out
  341.     lds    si,[bp+10]    ; in
  342.     mov    cx,[bp+14]    ; len
  343.     mov     bp,[bp+16]    ; k
  344.  
  345. ;; First multiply step has no carry in
  346.     lodsw
  347.     mul    bp
  348.     sub    es:[bx],ax    ; This time, store in [bx] directly
  349.     adc    dx,0
  350.     sub    bx,si        ; Prepare to use [bx+si].
  351.  
  352. ;; The switch() for Duff's device starts here
  353. ;; Note: this *is* faster than a jump table for an 8086 and '286.
  354. ;; 8086:  jump table: 44 cycles; this: 27/29/31/41
  355. ;; 80286: jump table: 25 cycles; this: 17/17/20/22
  356.     shr    cx,1
  357.     jc    SHORT ms16_odd
  358.  
  359.     inc    cx
  360.     shr    cx,1
  361.     jc    SHORT ms16_case2
  362.     jmp    SHORT ms16_case0
  363.  
  364. ms16_odd:
  365.     inc    cx
  366.     shr    cx,1
  367.     jnc    SHORT ms16_case1
  368.     jz    SHORT ms16_done    ; Avoid entire loop in this case
  369.  
  370. ms16_loop:
  371.     lodsw
  372.     mov    di,dx        ; Remember carry for later
  373.     mul    bp
  374.     add    ax,di        ; Add carry in from previous word
  375.     adc    dx,0
  376.     sub    es:[bx+si],ax
  377.     adc    dx,0
  378. ms16_case2:
  379.     lodsw
  380.     mov    di,dx        ; Remember carry for later
  381.     mul    bp
  382.     add    ax,di        ; Add carry in from previous word
  383.     adc    dx,0
  384.     sub    es:[bx+si],ax
  385.     adc    dx,0
  386. ms16_case1:
  387.     lodsw
  388.     mov    di,dx        ; Remember carry for later
  389.     mul    bp
  390.     add    ax,di        ; Add carry in from previous word
  391.     adc    dx,0
  392.     sub    es:[bx+si],ax
  393.     adc    dx,0
  394. ms16_case0:
  395.     lodsw
  396.     mov    di,dx        ; Remember carry for later
  397.     mul    bp
  398.     add    ax,di        ; Add carry in from previous word
  399.     adc    dx,0
  400.     sub    es:[bx+si],ax
  401.     adc    dx,0
  402.  
  403.     loop    ms16_loop
  404.  
  405. ms16_done:
  406.     mov    ax,dx
  407.     pop    di
  408.     pop    si
  409.     pop    ds
  410.     pop    bp
  411.     ret
  412.  
  413. _lbnMulSub1_16    endp
  414.  
  415. ;; Two-word by one-word divide.  Stores quotient, returns remainder.
  416. ;; BNWORD16 lbnDiv21_16(BNWORD16 *q, BNWORD16 nh, BNWORD16 nl, BNWORD16 d)
  417. ;;                      4            8            10           12
  418.     align    2
  419. _lbnDiv21_16    proc    far
  420.     mov    cx,bp        ; bp NOT pushed; note change in offsets
  421.     mov    bp,sp
  422.     mov    dx,[bp+8]
  423.     mov    ax,[bp+10]
  424.     div    WORD PTR [bp+12]
  425.     les    bx,[bp+4]
  426.     mov    es:[bx],ax
  427.     mov    ax,dx
  428.     mov    bp,cx
  429.     ret
  430.  
  431.     nop        ; To align loop in lbnModQ properly
  432.  
  433. _lbnDiv21_16    endp
  434.  
  435. ;; Multi-word by one-word remainder.
  436. ;; BNWORD16 lbnModQ_16(BNWORD16 *q, unsigned len, unsigned d)
  437. ;;                     6            10            12
  438. _lbnModQ_16    proc    far
  439.     push    bp
  440.     mov    bp,sp
  441.     push    ds
  442.     mov    bx,si
  443.     mov    cx,10[bp]    ; load len
  444.     lds    si,6[bp]    ; load q
  445.     std            ; loop MSW to LSW
  446.     add    si,cx
  447.     mov    bp,12[bp]    ; load d
  448.     add    si,cx
  449.     xor    dx,dx        ; Set up for first divide
  450.     sub    si,2        ; Adjust pointer to point to MSW
  451.  
  452.     lodsw            ; Load first word
  453.  
  454.     cmp    ax,bp        ; See if we can skip first divide
  455.     jnc    SHORT modq16_inner    ; No such luck
  456.     mov    dx,ax        ; Yes!  Modulus > input, so remainder = input
  457.     dec    cx        ; Do loop
  458.     jz    SHORT modq16_done
  459.  
  460. modq16_loop:
  461.     lodsw
  462. modq16_inner:
  463.     div    bp
  464.     loop    modq16_loop
  465. modq16_done:
  466.     pop    ds
  467.     mov    ax,dx    ; Return remainder
  468.     pop    bp
  469.     mov    si,bx
  470.     cld        ; Microsoft C's libraries assume this
  471.     ret
  472.  
  473. _lbnModQ_16    endp
  474.  
  475.  
  476. ;; Similar, but using 32-bit operations.
  477. ;;
  478. ;; The differences are that the switch() in Duff's device is done using
  479. ;; a jump table, and lods is not used because it's slower than load and
  480. ;; increment.  The pointers are only updated once per loop; offset
  481. ;; addressing modes are used, since they're no slower.  [di] is used
  482. ;; instead of [bx+si] because the extra increment of di take only one
  483. ;; cycle per loop a '486, while [bx+si] takes one extra cycle per multiply.
  484. ;;
  485. ;; The register assignments are also slightly different:
  486. ;;
  487. ;; es:[si]    in
  488. ;; ds:[di]    out
  489. ;; ecx        k
  490. ;; bp        loop counter (len/4)
  491. ;; edx,eax    high,low parts of product
  492. ;; ebx        carry word from previous multiply iteration
  493. ;;
  494. ;; The use of bp for a loop counter lets all the 32-bit values go
  495. ;; in caller-save registers, so there's no need to do any 32-bit
  496. ;; saves and restores.  Using ds:di for the destination saves one
  497. ;; segment override in the lbnMulN1_32 code, since there's one more
  498. ;; store to [di] than load from es:[si].
  499. ;;
  500. ;; Given the number of 32-bit references that this code uses, optimizing
  501. ;; it for the Pentium is interesting, because the Pentium has a very
  502. ;; inefficient implementation of prefix bytes.  Each prefix byte, with
  503. ;; the exception of 0x0f *>> on conditional branch instructions ONLY <<*
  504. ;; is a 1-cycle non-pairiable instruction.  Which has the effect of
  505. ;; forcing the instruction it's on into the U pipe.  But this code uses
  506. ;; *lots* of prefix bytes, notably the 0x66 operand size override.
  507. ;;
  508. ;; For example "add [di],eax" is advised against in Intel's optimization
  509. ;; papers, because it takes 3 cycles and 2 of them are not pairable.
  510. ;; But any longer sequence would have a prefix byte on every instruction,
  511. ;; resulting in even more non-pairable cycles.  Also, only two instructions
  512. ;; in the multiply kernel can go in the V pipe (the increments of si and
  513. ;; di), and they're already there, so the pairable cycles would be wasted.
  514. ;;
  515. ;; Things would be *quite* different in native 32-bit mode.
  516. ;;
  517. ;; All instructions that could go in the V pipe that aren't there are
  518. ;; marked.
  519. ;;
  520. ;; The setup code is quite intricately interleaved to get the best possible
  521. ;; performance out of a Pentium.  If you want to follow the code,
  522. ;; pretend that the sections actually come in the following order:
  523. ;; 1) prologue (push registers)
  524. ;; 2) load (fetch arguments)
  525. ;; 3) first multiply
  526. ;; 4) loop unrolling
  527. ;;
  528. ;; The loop unrolling setup consists of taking the count, adjusting
  529. ;; it to account for the first multiply, and splitting it into
  530. ;; two parts: the high bits are a loop count, while the low bits are
  531. ;; used to find the right entry in the Duff's device jump table and
  532. ;; to adjust the initial data pointers.
  533. ;;
  534. ;; Known slack: There is one instruction in the prologue and one in
  535. ;; the epilogue that could go in the V pipe if I could find a U-pipe
  536. ;; instruction to pair them with, but all the U-pipe instructions
  537. ;; are already paired, so it looks difficult.
  538. ;;
  539. ;; There is a cycle of Address Generation Interlock in the lbnMulN1_32
  540. ;; code on the Pentium (not on a '486).  I can't figure out how to
  541. ;; get rid of it without wasting time elsewhere.  The problem is that
  542. ;; the load of bx needs to be done as soon as possible to let it
  543. ;; be set up in time for the switch().  The other problem is the
  544. ;; epilogue code which can waste time if the order of the pushed
  545. ;; registers is diddled with so that ds doesn't come between si and di.
  546. ;;
  547. ;; The increment of si after the last load is redundant, and the
  548. ;; copy of the high word of the product to the carry after the last
  549. ;; multiply is likewise unnecessary.
  550. ;;
  551. ;; In these cases, the operations were done that way in order to remove
  552. ;; cycles from the loop on the '486 and/or Pentium, even though it costs
  553. ;; a few overhead cycles on a '386.
  554. ;; The increment fo si has to be done early because a load based on si
  555. ;; is the first thing in any given multiply step, and the address
  556. ;; generation interlock on the '486 and Pentium requires that a full
  557. ;; cycle (i.e. possibly two instructions on a Pentium) pass between
  558. ;; incrementing a register and using it in an address.
  559. ;; This saves one cycle per multiply on a '486 and Pentium, and costs
  560. ;; 2 cycles per call to the function on a '386 and 1 cycle on a '486.
  561. ;;
  562. ;; The carry word is copied where it is so that the decrement of the loop
  563. ;; counter happens in the V pipe.  The instruction between the decrement
  564. ;; of the loop counter and the branch should be a U-pipe instruction that
  565. ;; doesn't affect the flags.  Thus, the "mov" was rotated down from
  566. ;; the top of the loop to fill the slot.
  567. ;; This is a bit more marginal: it saves one cycle per loop iteration on
  568. ;; a Pentium, and costs 2 cycles per call on a '386, '486 or Pentium.
  569. ;;
  570. ;; The same logic applies to the copy of the carry and increment of si
  571. ;; before the test, in case 0, for skipping the loop entirely.
  572. ;; It makes no difference in speed if the loop is executed, but
  573. ;; incrementing si before saves an address generation interlock cycle
  574. ;; On a '486 and Pentium in the case that the loop is executed.
  575. ;; And the loop is executed more often than not.
  576. ;;
  577. ;; Given that just one multiply on a '386 takes 12 to 41 cycles (with the
  578. ;; average being very much at the high end of that) 4 cycles of additional
  579. ;; overhead per call is not a big deal.
  580. ;;
  581. ;; On a Pentium, it would actually be easier to *not* unroll the loop
  582. ;; at all, since the decrement and compare are completely hidden
  583. ;; in the V-pipe and it wouldn't cost anything to do them more often.
  584. ;; That would save the setup for the unrolling and Duff's device at the
  585. ;; beginning.  But the overhead for that is pretty minor: ignoring what's
  586. ;; hidden in the V pipe, it's two cycles plus the indirect jump.
  587. ;; Not too much, and special-casing the pentium is quite a hassle.
  588. ;; (For starters, you have to detect it, and since you're probably in
  589. ;; V86 mode, without access to the EFLAGS register to test the CPUID bit.)
  590.  
  591.  
  592.     align    16
  593. _lbnMulN1_32    proc    far
  594.  
  595.     push    bp        ; U    prologue    ** Could be V
  596.     mov    bp,sp        ; V    prologue
  597.     push    si        ; U    prologue    ** Could be V
  598.     mov    bx,[bp+14]    ; U    load len    ** Could be V (AGI!)r
  599.     push    ds        ; NP    prologue
  600.     les    si,[bp+10]    ; NP    load in
  601.     mov     ecx,[bp+16]    ; U    load k
  602.     dec    bx        ; V    loop unrolling
  603.     shl    bx,2        ; U    loop unrolling
  604.     push    di        ; V    prologue
  605.     lds    di,[bp+6]    ; NP    load out
  606.     mov    bp,bx        ; U    loop unrolling    ** Could be V
  607.     and    bx,12        ; V    loop unrolling
  608.  
  609. ;; First multiply step has no carry in.
  610.     mov    eax,es:[si]    ; U    first multiply
  611.     add    si,bx        ; V    loop unrolling
  612.     mul    ecx        ; NP    first multiply
  613.     mov    [di],eax    ; U    first multiply
  614.     add    di,bx        ; V    loop unrolling
  615.  
  616. ;; The switch() for Duff's device.  This jump table is (slightly!) faster
  617. ;; than a bunch of branches on a '386 and '486, and is probably better yet
  618. ;; on higher processors.
  619.     jmp    WORD PTR cs:m32_jumptable[bx]    ; NP    loop unrolling
  620.     align 2
  621. m32_jumptable:
  622.     dw    OFFSET m32_case0, 0
  623.     dw    OFFSET m32_case1, 0
  624.     dw    OFFSET m32_case2, 0
  625.     dw    OFFSET m32_case3, 0, 0, 0, 0    ; Get loop aligned properly
  626.  
  627. m32_case0:
  628.     add    si,16        ; U    Fix up si    ** Could be V
  629.     test    bp,bp        ; V
  630.     mov    ebx,edx        ; U    Remember carry for later
  631.     jbe    SHORT m32_done    ; V    Avoid entire loop if loop count is 0
  632.  
  633. m32_loop:
  634.     mov    eax,es:[si-12]    ; U
  635.     add    di, 16        ; V
  636.     mul    ecx        ; NP
  637.     add    eax,ebx        ; U    Add carry in from previous word
  638.     adc    edx,0        ; U
  639.     mov    [di-12],eax    ; U
  640. m32_case3:
  641.     mov    ebx,edx        ; U    Remember carry for later
  642.     mov    eax,es:[si-8]    ; U
  643.     mul    ecx        ; NP
  644.     add    eax,ebx        ; U    Add carry in from previous word
  645.     adc    edx,0        ; U
  646.     mov    [di-8],eax    ; U
  647. m32_case2:
  648.     mov    ebx,edx        ; U    Remember carry for later
  649.     mov    eax,es:[si-4]    ; U
  650.     mul    ecx        ; NP
  651.     add    eax,ebx        ; U    Add carry in from previous word
  652.     adc    edx,0        ; U
  653.     mov    [di-4],eax    ; U
  654. m32_case1:
  655.     mov    ebx,edx        ; U    Remember carry for later
  656.     mov    eax,es:[si]    ; U
  657.     mul    ecx        ; NP
  658.     add    eax,ebx        ; U    Add carry in from previous word
  659.     adc    edx,0        ; U
  660.     add    si,16          ; V
  661.     mov    [di],eax    ; U
  662.  
  663.     sub    bp,16        ; V
  664.     mov    ebx,edx        ; U    Remember carry for later
  665.     ja    m32_loop    ; V
  666.  
  667. m32_done:
  668.     mov    [di+4],edx    ; U
  669.     pop    di        ; V
  670.     pop    ds        ; NP
  671.     pop    si        ; U    ** Could be V
  672.     pop    bp        ; V
  673.     ret            ; NP
  674.  
  675. _lbnMulN1_32    endp
  676.  
  677.  
  678.     align    16
  679. _lbnMulAdd1_32    proc    far
  680.  
  681.     push    bp        ; U    prologue    ** Could be V
  682.     mov    bp,sp        ; V    prologue
  683.     push    ds        ; NP    prologue
  684.  
  685.     mov     ecx,[bp+16]    ; U    load k
  686.     mov    bx,[bp+14]    ; V    load len
  687.     push    di        ; U    prologue    ** Could be V
  688.     dec    bx        ; V    loop unrolling
  689.     lds    di,[bp+6]    ; NP    load out
  690.     shl    bx,2        ; U    loop unrolling
  691.     push    si        ; V    prologue
  692.     les    si,[bp+10]    ; NP    load in
  693.  
  694.     mov    bp,bx        ; U    loop unrolling    ** Could be V
  695.     and    bx,12        ; V    loop unrolling
  696.  
  697. ;; First multiply step has no carry in.
  698.     mov    eax,es:[si]    ; U    first multiply
  699.     add    si,bx        ; V    loop unrolling
  700.     mul    ecx        ; NP    first multiply
  701.     add    [di],eax    ; U    first multiply
  702.     adc    edx,0        ; U    first multiply
  703.     add    di,bx        ; V    loop unrolling
  704.  
  705. ;; The switch() for Duff's device.  This jump table is (slightly!) faster
  706. ;; than a bunch of branches on a '386 and '486, and is probably better yet
  707. ;; on higher processors.
  708.     jmp    WORD PTR cs:ma32_jumptable[bx]    ; NP    loop unrolling
  709.     align 2
  710. ma32_jumptable:
  711.     dw    OFFSET ma32_case0, 0
  712.     dw    OFFSET ma32_case1, 0
  713.     dw    OFFSET ma32_case2, 0
  714.     dw    OFFSET ma32_case3, 0, 0    ; To get loop aligned properly
  715.  
  716. ma32_case0:
  717.     add    si,16        ; U    Fix up si    ** Could be V
  718.     test    bp,bp        ; V
  719.     mov    ebx,edx        ; U    Remember carry for later
  720.     jbe    SHORT ma32_done    ; V    Avoid entire loop if loop count is 0
  721.  
  722. ma32_loop:
  723.     mov    eax,es:[si-12]    ; U
  724.     add    di, 16        ; V
  725.     mul    ecx        ; NP
  726.     add    eax,ebx        ; U    Add carry in from previous word
  727.     adc    edx,0        ; U
  728.     add    [di-12],eax    ; U
  729.     adc    edx,0        ; U
  730. ma32_case3:
  731.     mov    ebx,edx        ; U    Remember carry for later
  732.     mov    eax,es:[si-8]    ; U
  733.     mul    ecx        ; NP
  734.     add    eax,ebx        ; U    Add carry in from previous word
  735.     adc    edx,0        ; U
  736.     add    [di-8],eax    ; U
  737.     adc    edx,0        ; U
  738. ma32_case2:
  739.     mov    ebx,edx        ; U    Remember carry for later
  740.     mov    eax,es:[si-4]    ; U
  741.     mul    ecx        ; NP
  742.     add    eax,ebx        ; U    Add carry in from previous word
  743.     adc    edx,0        ; U
  744.     add    [di-4],eax    ; U
  745.     adc    edx,0        ; U
  746. ma32_case1:
  747.     mov    ebx,edx        ; U    Remember carry for later
  748.     mov    eax,es:[si]    ; U
  749.     mul    ecx        ; NP
  750.     add    eax,ebx        ; U    Add carry in from previous word
  751.     adc    edx,0        ; U
  752.     add    si,16          ; V
  753.     add    [di],eax    ; U
  754.     adc    edx,0        ; U
  755.  
  756.     sub    bp,16        ; V
  757.     mov    ebx,edx        ; U    Remember carry for later
  758.     ja    ma32_loop    ; V
  759.  
  760. ma32_done:
  761.     pop    si    ; U    ** Could be V
  762.     pop    di    ; V
  763.     mov    ax,dx    ; U    return value low    ** Could be V
  764.     pop    ds    ; NP
  765.     shr    edx,16    ; U    return value high
  766.     pop    bp    ; V
  767.     ret        ; NP
  768.  
  769. _lbnMulAdd1_32    endp
  770.  
  771.  
  772.     align    16
  773. _lbnMulSub1_32    proc    far
  774.  
  775.     push    bp        ; U    prologue    ** Could be V
  776.     mov    bp,sp        ; V    prologue
  777.     push    ds        ; NP    prologue
  778.  
  779.     mov     ecx,[bp+16]    ; U    load k
  780.     mov    bx,[bp+14]    ; V    load len
  781.     push    di        ; U    prologue    ** Could be V
  782.     dec    bx        ; V    loop unrolling
  783.     lds    di,[bp+6]    ; NP    load out
  784.     shl    bx,2        ; U    loop unrolling
  785.     push    si        ; V    prologue
  786.     les    si,[bp+10]    ; NP    load in
  787.  
  788.     mov    bp,bx        ; U    loop unrolling    ** Could be V
  789.     and    bx,12        ; V    loop unrolling
  790.  
  791. ;; First multiply step has no carry in.
  792.     mov    eax,es:[si]    ; U    first multiply
  793.     add    si,bx        ; V    loop unrolling
  794.     mul    ecx        ; NP    first multiply
  795.     sub    [di],eax    ; U    first multiply
  796.     adc    edx,0        ; U    first multiply
  797.     add    di,bx        ; V    loop unrolling
  798.  
  799. ;; The switch() for Duff's device.  This jump table is (slightly!) faster
  800. ;; than a bunch of branches on a '386 and '486, and is probably better yet
  801. ;; on higher processors.
  802.     jmp    WORD PTR cs:ms32_jumptable[bx]    ; NP    loop unrolling
  803.     align 2
  804. ms32_jumptable:
  805.     dw    OFFSET ms32_case0, 0
  806.     dw    OFFSET ms32_case1, 0
  807.     dw    OFFSET ms32_case2, 0
  808.     dw    OFFSET ms32_case3, 0, 0    ; To get loop aligned properly
  809.  
  810. ms32_case0:
  811.     add    si,16        ; U    Fix up si    ** Could be V
  812.     test    bp,bp        ; V
  813.     mov    ebx,edx        ; U    Remember carry for later
  814.     jbe    SHORT ms32_done    ; V    Avoid entire loop if loop count is 0
  815.  
  816. ms32_loop:
  817.     mov    eax,es:[si-12]    ; U
  818.     add    di, 16        ; V
  819.     mul    ecx        ; NP
  820.     add    eax,ebx        ; U    Add carry in from previous word
  821.     adc    edx,0        ; U
  822.     sub    [di-12],eax    ; U
  823.     adc    edx,0        ; U
  824. ms32_case3:
  825.     mov    ebx,edx        ; U    Remember carry for later
  826.     mov    eax,es:[si-8]    ; U
  827.     mul    ecx        ; NP
  828.     add    eax,ebx        ; U    Add carry in from previous word
  829.     adc    edx,0        ; U
  830.     sub    [di-8],eax    ; U
  831.     adc    edx,0        ; U
  832. ms32_case2:
  833.     mov    ebx,edx        ; U    Remember carry for later
  834.     mov    eax,es:[si-4]    ; U
  835.     mul    ecx        ; NP
  836.     add    eax,ebx        ; U    Add carry in from previous word
  837.     adc    edx,0        ; U
  838.     sub    [di-4],eax    ; U
  839.     adc    edx,0        ; U
  840. ms32_case1:
  841.     mov    ebx,edx        ; U    Remember carry for later
  842.     mov    eax,es:[si]    ; U
  843.     mul    ecx        ; NP
  844.     add    eax,ebx        ; U    Add carry in from previous word
  845.     adc    edx,0        ; U
  846.     add    si,16          ; V
  847.     sub    [di],eax    ; U
  848.     adc    edx,0        ; U
  849.  
  850.     sub    bp,16        ; V
  851.     mov    ebx,edx        ; U    Remember carry for later
  852.     ja    ms32_loop    ; V
  853.  
  854. ms32_done:
  855.     pop    si    ; U    ** Could be V
  856.     pop    di    ; V
  857.     mov    ax,dx    ; U    return value low    ** Could be V
  858.     pop    ds    ; NP
  859.     shr    edx,16    ; U    return value high
  860.     pop    bp    ; V
  861.     ret        ; NP
  862.  
  863. _lbnMulSub1_32    endp
  864.  
  865.  
  866.  
  867. ;; Just for interest's sake, here's a completely Pentium-optimized version.
  868. ;; In addition to being smaller, it takes 8 + (8+mul_time)*n cycles, as
  869. ;; compared to the 10 + jmp_time + (8+mul_time)*n cycles for the loop above.
  870. ;; (I don't know how long a 32x32->64 bit multiply or an indirect jump
  871. ;; take on a Pentium, so plug those numbers in.)
  872. ;    align    2
  873. ;    nop    ; To align loop nicely
  874. ;P_lbnMulAdd1_32    proc    far
  875. ;
  876. ;    push    bp        ; U    prologue    ** Could be V
  877. ;    mov    bp,sp        ; V    prologue
  878. ;    push    ds        ; NP    prologue
  879. ;    mov     ecx,[bp+16]    ; U    load k
  880. ;    push    si        ; V    prologue
  881. ;    lds    si,[bp+10]    ; NP    load in
  882. ;    mov    eax,[si]    ; U    first multiply
  883. ;    push    di        ; V    prologue
  884. ;    mul    ecx        ; NP    first multiply
  885. ;    les    di,[bp+6]    ; NP    load out
  886. ;    add    es:[di],eax    ; U    first multiply
  887. ;    mov    bp,[bp+14]    ; V    load len
  888. ;    adc    edx,0        ; U    first multiply
  889. ;    dec     bp        ; V
  890. ;    mov    ebx,edx        ; U    Remember carry for later
  891. ;    je    Pma32_done    ; V
  892. ;Pma32_loop:
  893. ;    mov    eax,[si+4]    ; U
  894. ;    add    di,4        ; V
  895. ;    mul    ecx        ; NP
  896. ;    add    eax,ebx        ; U    Add carry in from previous word
  897. ;    adc    edx,0        ; U
  898. ;    add    si,4        ; V
  899. ;    add    es:[di],eax    ; U
  900. ;    adc    edx,0        ; U
  901. ;    dec    bp        ; V
  902. ;    mov    ebx,edx        ; U    Remember carry for later
  903. ;    jne    Pma32_loop    ; V
  904. ;Pma32_done:
  905. ;    pop    di    ; U    ** Could be V
  906. ;    pop    si    ; V
  907. ;    pop    ds    ; NP
  908. ;    mov    ax,dx    ; U    return value low    ** Could be V
  909. ;    pop    bp    ; V
  910. ;    shr    edx,16    ; U    return value high
  911. ;    ret        ; NP
  912. ;
  913. ;P_lbnMulAdd1_32    endp
  914.  
  915.  
  916.  
  917. ;; Two-word by one-word divide.  Stores quotient, returns remainder.
  918. ;; BNWORD32 lbnDiv21_32(BNWORD32 *q, BNWORD32 nh, BNWORD32 nl, BNWORD32 d)
  919. ;;                      4            8            12           16
  920.     align    16
  921. _lbnDiv21_32    proc    far
  922.     mov    cx,bp            ; U    bp NOT pushed; offsets differ
  923.     mov    bp,sp            ; V
  924.                     ; AGI
  925.     mov    edx,[bp+8]        ; U
  926.     mov    eax,[bp+12]        ; U
  927.     div    DWORD PTR [bp+16]    ; NP
  928.     les    bx,[bp+4]        ; NP
  929.     mov    es:[bx],eax        ; U
  930.     mov    ax,dx            ; V
  931.     shr    edx,16            ; U
  932.     mov    bp,cx            ; V
  933.     ret                ; NP
  934.  
  935.     nop
  936.     nop
  937.     nop
  938.     nop                ; Get lbnModQ_32 aligned properly
  939.  
  940. _lbnDiv21_32    endp
  941.  
  942. ;; Multi-word by one-word remainder.
  943. ;; This speeds up key generation.  It's not worth unrolling and so on;
  944. ;; using 32-bit divides is enough of a speedup.
  945. ;;
  946. ;; bp is used as a counter so that all the 32-bit values can be in
  947. ;; caller-save registers (eax, ecx, edx).  bx is needed as a pointer.
  948. ;;
  949. ;; The modulus (in ebp) is 16 bits.  Given that the dividend is 32 bits,
  950. ;; the chances of saving the first divide because the high word of the
  951. ;; dividend is less than the modulus are low enough it's not worth taking
  952. ;; the cycles to test for it.
  953. ;;
  954. ;; unsigned lbnModQ_32(BNWORD16 *q, unsigned len, unsigned d)
  955. ;;                     6            10            12
  956. _lbnModQ_32    proc    far
  957.     xor    ecx,ecx        ; U    Clear ecx (really, the high half)
  958.     push    bp        ; V
  959.     mov    edx,ecx        ; U    Clear high word for first divide
  960.     mov    bp,sp        ; V
  961.     push    ds        ; NP
  962.     lds    ax,[bp+6]    ; NP    Load dividend pointer
  963.     mov    bx,[bp+10]    ; U    Load count    ** Could be V
  964.     sub    ax,4        ; V    Offset dividend pointer
  965.     mov    cx,[bp+12]    ; U    Load modulus    ** Could be V
  966.     mov    bp,bx        ; V    Copy count
  967.     shl    bx,2        ; U    Shift index
  968.     add    bx,ax        ; U    Add base    ** Could be V
  969. ;    lea    bx,[eax+ebp*4-4]; U    Move pointer to high word
  970.  
  971. modq32_loop:
  972.     mov    eax,[bx]    ; U
  973.     sub    bx,4        ; V
  974.     div    ecx        ; NP
  975.     dec    bp        ; U    ** Could be V
  976.     jnz    modq32_loop    ; V
  977. modq32_done:
  978.     pop    ds        ; NP
  979.     mov    ax,dx        ; U    ** Could be V
  980.     pop    bp        ; V
  981.     ret            ; NP
  982.  
  983. _lbnModQ_32    endp
  984.  
  985.  
  986. ;; int not386(void) returns 0 on a 32-bit (386 or better) processor;
  987. ;; non-zero if an 80286 or lower.  The Z flag is set to reflect
  988. ;; ax on return.  This is only called once, so it doesn't matter how
  989. ;; it's aligned.
  990.  
  991. _not386 proc    far
  992. ;;
  993. ;; This first test detects 80x86 for x < 2.  On the 8086 and '186,
  994. ;; "push sp" does "--sp; sp[0] = sp".  On all later processors, it does
  995. ;; "sp[-1] = sp; --sp".
  996. ;;
  997.     push    sp
  998.     pop    ax
  999.     sub    ax,sp
  1000.     jne    SHORT return
  1001.  
  1002. ;; This test is the key one.  It will probably detect 8086, V30 and 80186
  1003. ;; as well as 80286, but I haven't had access to test it on any of those,
  1004. ;; so it's protected by the well-known test above.  It has been tested
  1005. ;; on the 80286, 80386, 80486, Pentium and AMD tested it on their K5.
  1006. ;; I have not been able to confirm effectiveness on the P6 yet, although
  1007. ;; someone I spoke to at Intel said it should work.
  1008. ;;
  1009. ;; This test uses the fact that the '386 and above have a barrel shifter
  1010. ;; to do shifts, while the '286 does left shifts by releated adds.
  1011. ;; That means that on the '286, the auxilliary carry gets a copy of
  1012. ;; bit 4 of the shift output, while on the '386 and up, it's trashed
  1013. ;; (as it happens, set to 1) independent of the result.  (It's documented
  1014. ;; as undefined.)
  1015. ;;
  1016. ;; We do two shifts, which should produce different auxilliary carries
  1017. ;; on a '286 and XOR them to see if they are different.  Even on a
  1018. ;; future processor that does something different with the aux carry
  1019. ;; flag, it probably does something data-independent, so this will still
  1020. ;; work.  Note that all flags except aux carry are defined for shl
  1021. ;; output and will be the same for both cases.
  1022.  
  1023.     mov    al,4
  1024.     shl    al,1    ; Expected to produce ac = 0 on a '286
  1025.     lahf
  1026.     shl    al,1    ; Expected to produce ac = 1 on a '286
  1027.     mov    al,ah
  1028.     lahf
  1029.     xor    al,ah    ; Xor the flags together to detect the difference
  1030.     mov    ah,al    ; Clear ah if al is clear, leave Z flag alone
  1031. return:
  1032.     ret
  1033.  
  1034. _not386    endp
  1035.  
  1036. _TEXT    ends
  1037.  
  1038.     end
  1039.