fastspin compiler for P2: Assembly, Spin, BASIC, and C in one compiler

1313234363758

Comments

  • lazarus666lazarus666 Posts: 31
    edited 2019-12-23 - 09:16:54
    Rayman wrote: »
    Is there an easy way to find the end of program in HUB memory with Fastspin?

    Say I want to create a heap that starts at the end of the code...
    How can I find that point?

    If I make a DAT label at the bottom of the main .spin2 file, will that wind up on the bottom of the code?
    I've been searching the forum and my question seems to overlap with Ray's above, hence the quote.

    In my situation, I've got a bunch of font data in the hub. And *after* that, I've got some more (unrelated) data that is labeled at the beginning, like this:
    	...
    Tiles	byte	11, 3,10,10, 0,10, 3, 3, 2   '0
    	byte	 1,10, 0, 0,10, 0, 3, 3, 2   '1
    	byte	 3, 3,10,11, 3, 2, 3, 3, 2   '2
    	byte	 3, 3,10, 0, 3,10, 3, 3, 2   '3
    	...
    
    My question is: How do I get the starting address for the data …

    You might try something like this in Flex C, that is to say, try creating a blob of memory that you want to use as a private heap, by creating a static variable inside a function, then you can call that function when you want to zero the data, and then return a pointer to the beginning of the data. Then create a simple one or two line main that calls get_static_buffer and then which calls your spin or PASM code with the pointer to your buffer (or private heap) as the input parameter.
    #define MAXBINS 2048
    #define BinArraySize MAXBINS
    
    unsigned int *get_static_buffer()
    {
    	static unsigned int x[BinArraySize];
    	int i;
    	for (i=0;i<BinArraySize;i++)
    		x[i]=0;
    	return &(x[0]);
    }
    
  • JRoark wrote: »
    When using FlexGUI 4.0.5, under Win10 and coding in BASIC for the P2-EVAL rev B board:

    @ersmith It looks like there is a GUI issue when attempting to do a search (Edit->Find). Entering any search criteria gives this error:
    bad command "tag": must be configure, cget, invoke, instate, state, or identify
    bad command "tag": must be configure, cget, invoke, instate, state, or identify
    
    I'm not seeing this. Exactly what sequence of events gets you into this state? Does it still happen for you if you click inside the window containing the code before attempting the search?
  • In my situation, I've got a bunch of font data in the hub. And *after* that, I've got some more (unrelated) data that is labeled at the beginning, like this:
    	...
    Tiles	byte	11, 3,10,10, 0,10, 3, 3, 2   '0
    	byte	 1,10, 0, 0,10, 0, 3, 3, 2   '1
    	byte	 3, 3,10,11, 3, 2, 3, 3, 2   '2
    	byte	 3, 3,10, 0, 3,10, 3, 3, 2   '3
    	...
    
    My question is: How do I get the starting address for the data labeled by Tiles?

    On P2, just use "@ Tiles", so something like:
        mov myptr, ##@Tiles
    
    Note the double #, because the HUB address is almost certainly too big to fit in the 9 bits of a regular small immediate.

    On P1 the "@" does something different (in Spin-1 PASM it calculates a relative offset to the beginning of the object) and so you have to use triple @. I think triple @ should work fine on P2 as well, but it's a fastspin only construct not supported by PNut.

    (Writing "triple @" because the forum doesn't let me write three of those characters in a row...)
  • JRetSapDoogJRetSapDoog Posts: 836
    edited 2019-12-23 - 13:59:11
    Thanks for the replies, lazarus666 and ersmith. There are a few things like this that I have questions about, but this one was bugging me, so I decided to ask here. Thanks for your comments and assistance. Actually, at this point, I'm just working in PASM in one or two cogs and haven't even tried to organize things with a main program in a high-level language such as SPIN2 or C. That is to say, I'm mostly just experimenting for the sake of learning.

    To ersmith, thanks so much for the ##@ solution. It's existence is a relief! That's what I needed. Now I can get rid of the other hard-coded addresses, too. A week or so ago, shortly after getting my board, I asked myself how the double # symbol worked for big immediate values. And I wondered if the compiler was inserting a line behind the scenes. So at that time, I checked the listing file (for the first and only time (so far)), and I noticed that the compiler did insert a line to be able to handle this (or at least I think so). That's good to know when considering how much cog memory is being consumed (and also for calculating timing), but I don't recall reading about this in the big P2 doc. Maybe it's there, though.

    Of course, it's still early days as far as documentation (and to some extent tools) go. However, I sure would welcome something like the Propeller 1 manual, that gives descriptions and simple examples for all of the instructions, as well as other explanations. But creating such a document is a huge undertaking. Maybe the community could collaborate to compile such a document, separate from the current P2 document and the instruction listing. That instruction listing is hard to view in Google docs. I did download it, but it's still so wide that it's hard to use. However, it's still a must-have document.

    But in addition to a listing, a couple of paragraphs on each instruction would obviously be nice at some point in the future, especially for new users. For example, for the rep instruction, I think I'd use some verbiage to point out that a rep inside of a rep doesn't work (at least it won't likely do what one is expecting). And then it'd be good to point out why (for a deeper understanding). As I recall, the current P2 doc mentions that a branch of any kind ends the rep behavior. But an inexperienced user might think that a rep is somehow different than a branch. After all, rep manages to avoid the jump time penalty. So, that's an example of where a P1-style manual would come in handy. Maybe the compiler could issue a warning when trying to do that, too. Yes, I realize that we can make suggestions when viewing the current P2 doc, but I think that it has a different purpose or scope than the P1 manual. And to some extent, it's written more for those who have been following along with the development of the P2 in the forum over the years.

    Anyway, thanks again for the comments and assistance, guys. It's really good that we can get help here. Before I posted my question, I did try using the @ and # symbols together, but I forget what combinations I tried. The order seems so logical now, but I obviously didn't see it then. But thanks to this forum, now I know. So, just tell me where to send the check.
  • When using FlexGUI 4.0.5, under Win10 and coding in BASIC for the P2-EVAL rev B board:

    @ersmith I've got two little oddities to report.

    1). Something seems a little wonky with OPTION EXPLICIT and OPTION IMPLICIT in 4.0.5 vs earlier versions. According to the doc, OPTION IMPLICIT is the default, but if you read the doc with both eyes there is a caveat: you have to use LET in the assignment.

    This works fine:
    let test$ = "This is a test"
    print test$
    
    This throws an error:
    test$ = "This is a test"
    print test$
    
    The syntax using "LET" is pretty deprecated these days. Any (easy) way to allow OPTION IMPLICIT to work with the "LETless" syntax? (Disclaimer: I'd like to see IMPLICIT go away entirely and not give the user a choice but to define everything formally... but nobody asked me. heheh)

    2). I'm seeing the compiler apparently "forget" that OPTION EXPLICIT has been defined in the top file. I don't have it isolated to a demonstrable test case yet (the program is 11 include files and 4000+ lines of code), but I am seeing instances where even though OPTION EXPLICIT is clearly present in the beginning of the top file, I can sometimes just "whip-up" a new variable in the top file without defining it. None of the INCLUDEs have any OPTIONs defined within them, so I assume they'll take their cue from the OPTION EXPLICIT found in the top file when INCLUDEd? This behaviour seems to have changed with V4.0.5. Previously the compiler always rigidly enforced OPTION EXPLICIT.
  • @ersmith,
    Just getting back into doing some P2 programming so downloaded latest 4.0.5

    Noticed a warning for
                    mov     tmp,            lmm_x
    
    It is fine with the "-0"
                    mov     tmp,            lmm_x-0
    

    To me, the "-0" is wrong under these circumstances as I do want the value in the register labelled lmm_x, and I would think adding/moving/etc data between registers is quite common. In this instance, it is definately not an immediate, and shouldn't be confused as such.

    So, I'm just wondering what logic you are using to determine the warning?
  • JRoark wrote: »
    When using FlexGUI 4.0.5, under Win10 and coding in BASIC for the P2-EVAL rev B board:

    1). Something seems a little wonky with OPTION EXPLICIT and OPTION IMPLICIT in 4.0.5 vs earlier versions. According to the doc, OPTION IMPLICIT is the default, but if you read the doc with both eyes there is a caveat: you have to use LET in the assignment.
    I think that's always been the case. Basically (no pun intended :) ) OPTION IMPLICIT is just enough to let me get some really old BASIC demos working. Perhaps we can make it work without LET, but it seems like a very low priority (OPTION EXPLICIT is better...)
    2). I'm seeing the compiler apparently "forget" that OPTION EXPLICIT has been defined in the top file. I don't have it isolated to a demonstrable test case yet (the program is 11 include files and 4000+ lines of code), but I am seeing instances where even though OPTION EXPLICIT is clearly present in the beginning of the top file, I can sometimes just "whip-up" a new variable in the top file without defining it. None of the INCLUDEs have any OPTIONs defined within them, so I assume they'll take their cue from the OPTION EXPLICIT found in the top file when INCLUDEd? This behaviour seems to have changed with V4.0.5. Previously the compiler always rigidly enforced OPTION EXPLICIT.

    That's strange... I didn't intentionally change anything in the EXPLICIT/IMPLICIT code (or anywhere near it). There may be some side effect of some other changes, or maybe it's a bug that just hasn't been noticed before. I'll try to do some more experiments; my simple tests seemed to work, but I haven't looked at it for a while.

    Thanks for the bug reports,
    Eric
  • Cluso99 wrote: »
    Just getting back into doing some P2 programming so downloaded latest 4.0.5

    Noticed a warning for
                    mov     tmp,            lmm_x
    
    It is fine with the "-0"
                    mov     tmp,            lmm_x-0
    

    To me, the "-0" is wrong under these circumstances as I do want the value in the register labelled lmm_x, and I would think adding/moving/etc data between registers is quite common. In this instance, it is definately not an immediate, and shouldn't be confused as such.
    How was lmm_x declared? I'm guessing it was in a CON section. If you do something like:
    CON lmm_x = $1e0
    
    Then the assembler will warn about using lmm_x; however, if you declare it as a label like:
       org $1e0
    lmm_x res 1
    
    then there should be no warning. The logic is that literal integers and values declared as CON are expected to be (usually) be used as immediate values, whereas labels are expected to be registers.



  • ersmith wrote: »
    Cluso99 wrote: »
    Just getting back into doing some P2 programming so downloaded latest 4.0.5

    Noticed a warning for
                    mov     tmp,            lmm_x
    
    It is fine with the "-0"
                    mov     tmp,            lmm_x-0
    

    To me, the "-0" is wrong under these circumstances as I do want the value in the register labelled lmm_x, and I would think adding/moving/etc data between registers is quite common. In this instance, it is definately not an immediate, and shouldn't be confused as such.
    How was lmm_x declared? I'm guessing it was in a CON section. If you do something like:
    CON lmm_x = $1e0
    
    Then the assembler will warn about using lmm_x; however, if you declare it as a label like:
       org $1e0
    lmm_x res 1
    
    then there should be no warning. The logic is that literal integers and values declared as CON are expected to be (usually) be used as immediate values, whereas labels are expected to be registers.



    Yes, you got it in one :)

    Not sure I like the -0 tacked onto a label, but I don't have any other solution either. I specifically stopped using RES as it forced and ORG $1E0 although I suppose I could do
    RES $1E0-$

    Meanwhile, I changed my code to suppress the warning :)
  • Cluso99 wrote: »
    ersmith wrote: »
    How was lmm_x declared? I'm guessing it was in a CON section. If you do something like:
    CON lmm_x = $1e0
    
    Then the assembler will warn about using lmm_x; however, if you declare it as a label like:
       org $1e0
    lmm_x res 1
    
    then there should be no warning. The logic is that literal integers and values declared as CON are expected to (usually) be used as immediate values, whereas labels are expected to be registers.

    Yes, you got it in one :)

    Not sure I like the -0 tacked onto a label, but I don't have any other solution either. I specifically stopped using RES as it forced and ORG $1E0 although I suppose I could do
    RES $1E0-$

    Meanwhile, I changed my code to suppress the warning :)

    I don't quite see why the ORG $1E0 is a problem; you can just put it at the end of all your other code, e.g.:
    ... stuff using lmm_x ...
        fit $1e0  ' make sure it fits
        org $1e0
    lmm_x res 1  ' lmm_x = $1e0
    lmm_y res 1  ' lmm_y = $1e1
    ... etc ...
    

    None of the stuff after and including "fit" will actually produce any output in the binary, so it doesn't take up space.

    I'm certainly open to other suggestions about how the compiler could figure out when a CON is intended for use as a register and when it really is just a constant. I guess we could have some kind of standardized comment.
  • Eric,
    My lmm_xxx CON equates are defined in an include file. They are variables used by re-entrant hubexec code that is added once (or is already loaded in the ROM part of hub ram).

    They may be used by any cog, but that cog must reserve those 16 registers at $1E0.

    This is how I use those 16 registers. It just looks wrong to add the "-0" to the label, but it's no biggie either.
  • Did the syntax for including a library change?
    Seems I need to change "-L" to "-I" to include a library and also keep the source folder in the path...
  • Rayman wrote: »
    Did the syntax for including a library change?
    Seems I need to change "-L" to "-I" to include a library and also keep the source folder in the path...

    No, nothing has changed in the library handling lately. Both -I and -L are handled by the same code (they're in the same if statement in fastspin.c) so something else must have changed in your environment...
  • Something is different... With "-L", it won't check the source folder for the object files.
  • Rayman wrote: »
    Something is different... With "-L", it won't check the source folder for the object files.

    -L and -I are handled by the exact same code. There's no difference internally between them
            } else if (!strncmp(argv[0], "-L", 2) || !strncmp(argv[0], "-I", 2)) {
                const char *opt = argv[0];
                const char *incpath;
                char optchar[3];
                argv++; --argc;
                // save the -L or -I
                strncpy(optchar, opt, 2);
                optchar[2] = 0;
                if (opt[2] == 0) {
                    if (argv[0] == NULL) {
                        fprintf(stderr, "Error: expected another argument after %s\n", optchar);
                        exit(2);
                    }
                    opt = argv[0];
                    argv++; --argc;
                } else {
                    opt += 2;
                }
                opt = strdup(opt);
                incpath = opt;
                pp_add_to_path(&gl_pp, incpath);
          }
    
  • JRoarkJRoark Posts: 438
    edited 2019-12-30 - 17:03:14
    I'm missing something here, and I'm hoping wiser eyes will educate me on what it is. I'm trying to get the upper 32 bits of the Prop2 RevB system counter from FlexBASIC. Simple, right? Except this doesn't work and I'm at a loss as to why:
    FUNCTION CNTH() as integer
    ' Returns the high-order 32 bits of the system counter P2 Rev B and later
    	dim syscntr as long
    	ASM
    		MOV    syscntr, #0xFFFF_FFFF   WC   'set WC high
    		ROL    syscntr, #1   WC             'belt and suspenders here after MOV didnt seem to set WC
    		GETCT  syscntr                      'move high-order 32 bits of system cntr it syscntr 
    	END ASM
    	return syscntr
    END FUNCTION
    
    According to the most recent docs, when using GETCT, if WC is set, it will return the upper 32 bits of the 64-bit system counter. I'm setting WC with the MOV. But no bueno. So just to make sure I wasn't milking without a bucket, I ROL'd it thru WC. Still no dice.

    What am I missing here?

    EDIT: The code works, but it returns the lower 32 bits instead of the upper 32 bits.
  • potatoheadpotatohead Posts: 10,066
    edited 2019-12-30 - 17:23:21
    I am not where I can check, but is the bit in the instruction that sets the WC behavior the trigger, or the state of the C flag the trigger?

    Try "GETCT syscntr wc"

    The docs should say, "If the C flag is set..." if the flag state is the trigger. What they do say is "if WC is set", which speaks to the WC bit in the instruction, not the C flag bit.

    If it is the latter, then it makes sense to understand what gets written to the C flag too. I seem to recall overflow or something associated with all this.
  • potatohead wrote: »
    I am not where I can check, but is the bit in the instruction that sets the WC behavior the trigger, or the state of the C flag the trigger?

    Try "GETCT syscntr wc"

    The docs should say, "If the C flag is set..." if the flag state is the trigger. What they do say is "if WC is set", which speaks to the WC bit in the instruction, not the C flag bit.

    Good question.
    From the P2 Manual: "System counter extended to 64 bits. GETCT WC retrieves upper 32-bits".
    From the P2 Instruction Sheet: "GETCT D {WC} Get CT[31:0] or CT[63:32] if WC into D. GETCT WC + GETCT gets full CT. CT=0 on reset, CT++ on every clock. C = same.

    So the way I read that is: "the state of the WC bit triggers the behavior of GETCT. If WC is set, GETCT returns the upper 32 bits. If WC is not set, GETCT returns lower 32 bits".
  • The carry bit itself is called "C", not "WC". "WC" is a bit in the instruction which says that the instruction should set the C bit. So @potatohead is absolutely right. if you want to fetch the upper 32 bits you should do:
    asm
      getcnt counter_hi wc
    end asm
    

    to get all 64 bits do:
    asm
      getcnt counter_hi wc
      getcnt counter_lo
    end asm
    
    the "wc" on the instruction not only fetches the upper 32 bits, but also arranges that the following getcnt will fetch a low value that's consistent with the hi value in the case of wrap around.
  • JRoarkJRoark Posts: 438
    edited 2019-12-30 - 18:17:01
    @ersmith
    asm
      getcnt counter_hi wc
    end asm
    
    This code gives me: error: syntax error, unexpected identifier `counter_hi'. I think getcnt isn't an assembler instruction. But getct is, so I think what you meant is:
    asm
      getct counter_hi wc
    end asm
    
    But this returns the error: "error: modifier wc not valid for getct"

    Am I just being unusually obtuse here or what? Totally confuzzified...
  • Yes, sorry, "getct" and not "getcnt". And it turns out that I missed updating the definition of "getct" for the Rev B silicon changes :( so currently fastspin doesn't let you write "getct wc". I will fix that ASAP.
  • JRoarkJRoark Posts: 438
    edited 2019-12-30 - 18:20:59
    ersmith wrote: »
    Yes, sorry, "getct" and not "getcnt". And it turns out that I missed updating the definition of "getct" for the Rev B silicon changes :( so currently fastspin doesn't let you write "getct wc". I will fix that ASAP.

    You are a good man in a storm, Eric. Thank you for all you do! (And I must say that I'm pleased to find I'm not completely nuts... yet.... hehehe)

    Thanks also to @potatohead for the assist too.

  • ersmith wrote: »
    -L and -I are handled by the exact same code. There's no difference internally between them

    Very strange... Today they both work and do work the same...

  • There's a new fastspin binary release up on github in the usual place. It has the getct bug fix as well as several others.

  • I haven't read all the comments above this one.
    But I think I ran into the same problem with res and org
    But my code doesn't work with 4.0.6. It does work with 4.0.5.
    I don't have the time now to search for the problem or a way around it, so I will use 4.0.5 for now and come back to this in a few days.
    My code shrinks from 133632 bytes to 133600 bytes.
  • jef_vt wrote: »
    I haven't read all the comments above this one.
    But I think I ran into the same problem with res and org
    But my code doesn't work with 4.0.6. It does work with 4.0.5.
    I don't have the time now to search for the problem or a way around it, so I will use 4.0.5 for now and come back to this in a few days.
    My code shrinks from 133632 bytes to 133600 bytes.

    Sorry, what do you mean by "the same problem with res and org"? Neither res nor org changed between 4.0.5 and 4.0.6.
  • Dave HeinDave Hein Posts: 6,047
    edited 2020-01-05 - 03:11:13
    The following C code shows a bug in the fastspin C compiler. test2() works correctly, and test1() does not. test1() is maintaining the result at 8 bits instead of keeping all 32 bits.
    #include <stdio.h>
    
    #ifdef __FLEXC__
    #include <propeller.h>
    #define P2_TARGET_MHZ 160
    #include "sys/p2es_clock.h"
    #define BAUD 230400
    #endif
    
    int test1(int x, unsigned char y)
    {
        return y | (x << 8);
    }
    
    int test2(int x, unsigned char y)
    {
        return (x << 8) | y;
    }
    
    void main(void)
    {
    #ifdef __FLEXC__
        clkset(_SETFREQ, _CLOCKFREQ);
        _setbaud(BAUD);
    #endif
        printf("%8.8x %8.8x\n", test1(0x12, 0x34), test2(0x12, 0x34));
    }
    

    EDIT: I've attached the file test.c, where I added passing variables to test1() and test2(). I noticed when passing constants the compiler just pre-computes the result. When passing variables, assembly code is generated to do the SHIFT and OR functions. test1() also generates an erroneous AND to mask off the upper bits.
    c
    c
    630B
  • When using FlexGUI 4.1.0-beta, under Win10 and coding in BASIC for the P2-EVAL rev B board:

    @ersmith
    Question One: Why does this code throw the error: "child killed: segmentation violation"?
    	option base 0
    	option explicit
    
    	dim aa(5) as integer
    	dim p as integer
    	aa(0) = 4
    	aa(1) = 6
    	aa(2) = 1
    	aa(3) = 7
    	aa(4) = 5
    	aa(5) = 9
    
    	print "Starting Values:"
    	for p = 0 to 5
    		print "aa("; p; ") = "; aa(p)
    	next p
    	print 
    		
    	BubbleSort(aa())
    
    	print "Ending Values:"
    	for p = 0 to 5
    		print "aa("; p; ") = "; aa(p)
    	next p
    
    SUB BubbleSort(aa as integer) 
    '
    ' Performs an ascending (small to large) bubble sort
    
    	dim i as integer
    	dim j as integer
    	dim n as integer
    	dim x as integer
    
    	n = n
    
    	for i = 1 to n - 1
    		for j = 1 to n - i
    			if myArray(j) > myArray(j+1) then
    				x = myArray(j)
    				myArray(j) = myArray(j+1)
    				myArray(j+1) = x 
    			end if
    		next j
    	next i
    END SUB
    
    Here is the full error:
    "d:/Flex2Gui/flexgui/bin/fastspin" -2 -l -O1 -I "d:/Flex2Gui/flexgui/include" -I "d:/Flex2Gui/flexgui/P2 Libs"  "d:/Flex2Gui/flexgui/P2 Libs/SortLibP2.bas"
    Propeller Spin/PASM Compiler 'FastSpin' (c) 2011-2019 Total Spectrum Software Inc.
    Version 4.1.0-beta-daaa5547 Compiled on: Jan  2 2020
    child killed: segmentation violation
    Finished at Sat Jan  4 22:42:30 2020
    

    Question Two: How do you pass an array to a function or sub?

    Question Three: How can I determine LBOUND and UBOUND of an array?

    (If you haven't noticed, tonight was "array night", and it's slaying me. hehehe)
  • Dave Hein wrote: »
    The following C code shows a bug in the fastspin C compiler. test2() works correctly, and test1() does not. test1() is maintaining the result at 8 bits instead of keeping all 32 bits.
    Thanks for the bug report, Dave! There was a missing check for promoting types. I've fixed that in github and it'll be in the next release.

    Eric
  • JRoark wrote: »
    When using FlexGUI 4.1.0-beta, under Win10 and coding in BASIC for the P2-EVAL rev B board:

    @ersmith
    Question One: Why does this code throw the error: "child killed: segmentation violation"?
    It's a compiler bug, of course :). It's crashing because it can't handle the expression "BubbleSort(aa())"; it's trying to parse "aa()" as a function call and crashing because it's missing a sanity check. The crash is fixed in github now, although it's still not doing what you want (it's printing an error for now).
    Question Two: How do you pass an array to a function or sub?

    I'll try to adjust the syntax to something at least vaguely FreeBasic compatible. For now you'll have to use pointers (much like in C):
    	option base 0
    	option explicit
    
    	dim aa(5) as integer
    	dim p as integer
    	aa(0) = 4
    	aa(1) = 6
    	aa(2) = 1
    	aa(3) = 7
    	aa(4) = 5
    	aa(5) = 9
    
    SUB BubbleSort(myArray as integer pointer, siz as integer) 
    '
    ' Performs an ascending (small to large) bubble sort
    
    	dim j as integer
    	dim n as integer
    	dim x as integer
    	dim i as integer
    	
    	n = siz
    	for i = n - 1 to 1 step -1
    	    for j = 0 to i
    	        if myArray(j) > myArray(j+1) then
    		    x = myArray(j)
    		    myArray(j) = myArray(j+1)
    		    myArray(j+1) = x
    	        end if
    		next j
    	next i
    END SUB
    
    	print "Starting Values:"
    	for p = 0 to 5
    		print "aa("; p; ") = "; aa(p)
    	next p
    	print 
    		
    	BubbleSort(aa, 5)
    
    	print "Ending Values:"
    	for p = 0 to 5
    		print "aa("; p; ") = "; aa(p)
    	next p
    
    
    Question Three: How can I determine LBOUND and UBOUND of an array?

    You can't, unfortunately, and that's unlikely to change anytime soon. Inside a sub or function the LBOUND is pretty much always going to be whatever you set as the option base, but the UBOUND will have to be passed as a parameter.

    Thanks for the bug reports!

    Eric
Sign In or Register to comment.