Jump to content
IGNORED

cycles used by bB operations


RevEng

Recommended Posts

The stella method you outline is good for simple short code, but often it's more complex code you're interested in measuring. With my method, even when there are subroutines and bankswitching involved, I can get a "good enough" answer literally in a few seconds. Then I can tweak and re-time the code without having to step into subroutines and add up dozens or hundreds of opcode timings.

That's true, and I wasn't trying to find fault with your method and results. But the title of your thread is "cycles used by bB operations," so I thought you were trying to determine the cycles used by specific bB instructions, as opposed to a section of more complex code.

 

To measure the cycles used by specific instructions, the methods I used are more exact, and it would be nice to have a reference listing the cycles used by all the different sorts of bB instructions (which was suggested some time back in another thread). The stepping method and noting the frame cycles should only be needed when bankswitching is involved, or when calling subroutines is involved (such as for scrolling or certain math operations).

 

One fallacy of that kind of single-instruction cycle listing is that the exact cycles might vary depending on which branches are taken, possible page-crossing differences from one program to another, how many times a loop needs to be performed, etc. Another fallacy is that most code is made up of complex instructions that combine different single instructions, and you can't necessarily get a good idea of how long a complex instruction will take by adding up the cycles used by all the single instructions that make up the complex instruction. A third fallacy is that changes in bB's builtin routines or the way that specific instructions are compiled into equivalent assembly code may conceivably occur from one version to another, either deliberately (for better optimization) or by accident (bugs). So a cycle listing for different instructions might be correct for one version, but incorrect for other versions of bB. Still, it would probably be good to have such a listing to help bB programmers recognize where something they're doing, or the way they're doing it, might be eating up a lot of cycles.

 

Michael

Link to comment
Share on other sites

. . . and it would be nice to have a reference listing the cycles used by all the different sorts of bB instructions (which was suggested some time back in another thread).

I added a few of the ones you posted to the bB page last night. As you posted, there are things you have to ignore (hop over here, don't look at that, jump over there, duck!). It's a job for a big brain. There are too many ways for me to screw it up and the cycle count would be wrong and useless.

Link to comment
Share on other sites

I added a few of the ones you posted to the bB page last night.

I hope it was just the counts, not the assembly code! ;)

 

I decided to try making a list of cycle counts for the things you list in the index on your bB page, starting with "{} (Bit Operations)" and working my way through all the bB statements or operations that you describe. It's going to take a long time, but here's what I have so far...

 

a{0} = 0 : rem * turn a bit off

a{0} = 1 : rem * turn a bit on

Each of these takes 8 cycles.

 

a{0} = !a{0} : rem * flip a bit on or off

If the bit is off (flipping to on), this takes 24 cycles.

If the bit is on (flipping to off), this takes 23 cycles (or 24 cycles if a page-crossing occurs).

 

a{0} = a{1} : rem * set one bit to another bit of the same variable

a{0} = b{0} : rem * set one bit to another bit of a different variable

If the bit being copied is off, this takes 23 cycles (or 24 cycles if a page-crossing occurs).

If the bit being copied is on, this takes 24 cycles.

 

a{0} = !a{1} : rem * set one bit to the opposite of another bit of the same variable

If the bit being copied/flipped is off, this takes 24 cycles.

If the bit being copied/flipped is on, this takes 23 cycles (or 24 cycles if a page-crossing occurs).

 

Still to do:

 

a{0} = !b{0}

I expect this to be the same as a{0} = !a{1}.

 

a{0} = b

a{0} = !b

 

if a{0} then ...

if !a{0} then ...

if a{0} then branch

if !a{0} then branch

if a{0} then goto

if !a{0} then goto

 

etc.

 

This process is actually pretty interesting, because it's making me wonder if certain operations could possibly be optimized during compilation to make them faster/smaller.

 

Michael

Link to comment
Share on other sites

By the way, your page correctly says that you can't use dim to assign variable names to bits, but I didn't see you mention using def to do this. The def statement was added a while back, and can be used to assign variable names to bits, as follows:

 

  def game_over_flag = a{0}

  game_over_flag = 0
  game_over_flag = 1
  if game_over_flag then goto game_over_routine
  if !game_over_flag then goto moose_tracks

Everywhere bB sees game_over_flag, it will replace it with a(0).

 

You could also use def to define nibbles, as follows:

 

  def a_lo = a & $0F
  def a_hi = (a & $F0) / 16

  if a_lo > 9 then goto saucer_biscuits
  if a_hi > 9 then goto snuggle_bunnies

But you can't use them to *set* the nibbles, because statements like "a & $0F = 8" or "(a & $F0) / 16 = 4" are invalid. In other words, you can use "a_lo" anywhere that you *can* use "a & $0F" but not anywhere that you *can't* use "a & $0F" (since bB will replace "a_lo" with "a & $0F" when it's compiling your code).

 

Michael

Edited by SeaGtGruff
Link to comment
Share on other sites

That's true, and I wasn't trying to find fault with your method and results. But the title of your thread is "cycles used by bB operations," so I thought you were trying to determine the cycles used by specific bB instructions, as opposed to a section of more complex code.

True enough that's how it started, but even then certain bB instructions that look atomic do switch banks and jump into subroutines without the user necessarily being aware of it. (a=b/3, the pf* commands, ...).

 

In truth the thread wasn't meant to be a definitive cycle-accurate list - just a stab at how relatively expensive certain common instructions actually were. Everybody talks about how bad non-power-of-2 divisions are, for example, but it was interesting to see the scale of the issue.

 

Totally agreed that a definitive cycle-chart would be great, but problematic to create. You'd need at least 2 values for each operation in non-bankswitched and bankswitched binaries, then you'd need asterix exceptions for stuff like pfscroll, and all the other issues you noted.

 

It might be better to just create a graph of relative cycle times, without a scale. or with a scale but without scale dividers. The point being it's relative expense that's being displayed. Certain operations that are highly variable (like non-power-of-2 divisions) might show a fuzzy/transparent area from the minimum cycle to maximum cycle time.

 

My 2 cents anyway.

Link to comment
Share on other sites

I added a few of the ones you posted to the bB page last night.

I hope it was just the counts, not the assembly code! ;)

Yep, here's an example:

 

http://www.randomterrain.com/atari-2600-memories-batari-basic-commands.html#goto

 

Don't know if we want to put it in a side box like that or do it some other way.

 

 

 

 

I decided to try making a list of cycle counts for the things you list in the index on your bB page, starting with "{} (Bit Operations)" and working my way through all the bB statements or operations that you describe. It's going to take a long time, but here's what I have so far...

That will help a lot. :thumbsup:

 

 

 

 

By the way, your page correctly says that you can't use dim to assign variable names to bits, but I didn't see you mention using def to do this. The def statement was added a while back, and can be used to assign variable names to bits, as follows:

 

  def game_over_flag = a{0}

  game_over_flag = 0
  game_over_flag = 1
  if game_over_flag then goto game_over_routine
  if !game_over_flag then goto moose_tracks

Everywhere bB sees game_over_flag, it will replace it with a(0).

 

You could also use def to define nibbles, as follows:

 

  def a_lo = a & $0F
  def a_hi = (a & $F0) / 16

  if a_lo > 9 then goto saucer_biscuits
  if a_hi > 9 then goto snuggle_bunnies

But you can't use them to *set* the nibbles, because statements like "a & $0F = 8" or "(a & $F0) / 16 = 4" are invalid. In other words, you can use "a_lo" anywhere that you *can* use "a & $0F" but not anywhere that you *can't* use "a & $0F" (since bB will replace "a_lo" with "a & $0F" when it's compiling your code).

 

Michael

Thanks. batari hasn't had time to write up something about def yet. I know almost nothing about it or where it should go on the bB page.

Link to comment
Share on other sites

By the way, your page correctly says that you can't use dim to assign variable names to bits, but I didn't see you mention using def to do this. The def statement was added a while back, and can be used to assign variable names to bits, as follows:

Thanks. batari hasn't had time to write up something about def yet. I know almost nothing about it or where it should go on the bB page.

There's some brief information about the def command, as well as other additions or changes, in the following two threads:

 

Illegal Character Error in DASM

 

10-25-08 bAtari Basic build?

 

Michael

 

PS -- I would suggest adding it in the same area as the dim statement, but put it after the dim statement. And you can reference it, and give examples of it, in places where it would be useful, like in the section about bit operations when you say that dim can't be used to assign a variable name to a bit. Right after the part where you give bad examples, you could say, "Instead, you would need to use the def statement to do that," and then give examples of using def to assign variable names to bit variables, and using them in program statements.

Edited by SeaGtGruff
Link to comment
Share on other sites

gosub_with_bankswitch+return : 122 cycles

gosub_with_bankswitch+return_with_bankswitch : 110 cycles

So the first one is gosub+return, and the second is gosub+return otherbank? I guess there's not that much of an improvement by using otherbank.

 

What may also be useful is measuring both when the subroutine is called from the same bank. return should be quick, and a return otherbank should be slower, but by how much is a good thing to know.

Link to comment
Share on other sites

There's some brief information about the def command, as well as other additions or changes, in the following two threads:

 

Illegal Character Error in DASM

 

10-25-08 bAtari Basic build?

 

Michael

 

PS -- I would suggest adding it in the same area as the dim statement, but put it after the dim statement. And you can reference it, and give examples of it, in places where it would be useful, like in the section about bit operations when you say that dim can't be used to assign a variable name to a bit. Right after the part where you give bad examples, you could say, "Instead, you would need to use the def statement to do that," and then give examples of using def to assign variable names to bit variables, and using them in program statements.

Thanks. I'll start working on it.

Link to comment
Share on other sites

Looks like I'll need to add macro and callmacro too.

 

I need to get some sleep, but later on I'll Google DEF to see if I can get more hints and tips about it. For example, do you need to DIM an alias before you use it with DEF or is it totally separate?

Edited by Random Terrain
Link to comment
Share on other sites

Looks like I'll need to add macro and callmacro too.

 

I need to get some sleep, but later on I'll Google DEF to see if I can get more hints and tips about it. For example, do you need to DIM an alias before you use it with DEF or is it totally separate?

The def statement is totally separate from dim. It lets you assign one text string to another, and you can use it to create a variable name (string) that will be replaced with another string when your program is compiled.

 

  def a_squared = a * a

  if a_squared = 144 then goto giggling_wolverines

But you can't necessarily use the new string the same way you can use a variable, because you can use it only in places where using the old string is valid. For instance, in the example above you can say "if a_squared = 144," because "if a * a = 144" is valid. But you wouldn't be able to say "a_squared = 144," because "a * a = 144" isn't valid.

 

Although def and dim are totally separate, you can use them in combination if you want:

 

  dim game_flags = a

  def game_level = game_flags & $0F
  def show_title = game_flags{4}
  def game_in_play = game_flags{5}
  def game_over = game_flags{6}
  def show_hi_scores = game_flags{7}

Michael

Link to comment
Share on other sites

Looks like I'll need to add macro and callmacro too.

There are a couple of other new commands or changes in commands, like support for 0840 bankswitching, an "extra" command, and "on...gosub" (which you already know about). I tried to make a list from batari's posts, but I may have missed something, and this doesn't include any changes in the newest build:

 

(1) def can be used to assign a name to a bit variable, e.g.: def GAMEFLAG = a{1}

 

(2) def lets you replace a string with another string, so it can do more than just define bit variables

 

(3) macro command lets you define macros

 

(4) callmacro command lets you call macros

 

(5) const increased from a maximum of about 50 to a maximum of 500

 

(6) on..gosub

 

(7) the number of redefined variables increased from 100 to 500

 

(8 ) the postprocessor will let you know if a user-defined header or asm file is used instead of the default

 

(9) a version message is displayed

 

(10) bugfixes to multicolored playfields

 

(11) optimizations to joystick and console switch reading code

 

(12) support for 0840 banking (use romsize 8kEB)

 

(13) a warning if any includes are ignored due to improper placement

 

(14) pfscrolling in multisprite kernel (needs asm support to work)

 

(15) optimized storage of some sprite data

 

(16) optimized complex math

 

(17) optimized bit access

 

(18) optimized score addition

 

(19) bugfix to allow fixed-point number without needing a decimal in it

 

(20) peephole optimizer (-O switch) improved

 

(21) an obscure command called "extra" whose sole purpose is to place data or inline asm in the otherwise wasted space caused by placement of sprite data to prevent page wraps. For some reason it does not work with code (I can't remember why.) If you are writing a game using the multisprite kernel and the last bank is full, using extra might help you recover some of that space (which could be several hundred bytes.)

The syntax is:

extraN:

Where N is a number. End the extra block with "end," not indented, just like data statements, sprite definitions, etc. 0 will attempt to place your data or inline asm in the first block of wasted space, if it exists. 1 will place it in the second, 2 in the third, and so on. I can't remember what happens if the block doesn't exist.

A code example for extra might look like this:

  extra0:

  data getsound
  2,4,15,8
end

  asm
  lda #1
  ...
end

end

Michael

Edited by SeaGtGruff
Link to comment
Share on other sites

There are a couple of other new commands or changes in commands, like support for 0840 bankswitching, an "extra" command, and "on...gosub" (which you already know about). I tried to make a list from batari's posts, but I may have missed something, and this doesn't include any changes in the newest build:

There's also improved keyword detection, and bB now supports complex conditions in if-thens. You alluded to the latter in a previous post but I didn't see it in the list.

Link to comment
Share on other sites

(21) an obscure command called "extra" whose sole purpose is to place data or inline asm in the otherwise wasted space caused by placement of sprite data to prevent page wraps. For some reason it does not work with code (I can't remember why.) If you are writing a game using the multisprite kernel and the last bank is full, using extra might help you recover some of that space (which could be several hundred bytes.)

The syntax is:

extraN:

Where N is a number. End the extra block with "end," not indented, just like data statements, sprite definitions, etc. 0 will attempt to place your data or inline asm in the first block of wasted space, if it exists. 1 will place it in the second, 2 in the third, and so on. I can't remember what happens if the block doesn't exist.

A code example for extra might look like this:

  extra0:

  data getsound
  2,4,15,8
end

  asm
  lda #1
  ...
end

end

I just tried the "extra0" command, and what it did was put the data and code inside a *macro* named "extra0"! I don't know if that's because there wasn't any extra space for the compiler to put the data and code, although I did use a "player0:" statement first, to hopefully create some wasted space in front of the player0 data. Even when I made the sprite tall enough to force a page-alignment, the "extra0" data and code still ended up being defined as a macro. :(

 

Michael

Link to comment
Share on other sites

The def statement is totally separate from dim. It lets you assign one text string to another, and you can use it to create a variable name (string) that will be replaced with another string when your program is compiled.

 

  def a_squared = a * a

  if a_squared = 144 then goto giggling_wolverines

But you can't necessarily use the new string the same way you can use a variable, because you can use it only in places where using the old string is valid. For instance, in the example above you can say "if a_squared = 144," because "if a * a = 144" is valid. But you wouldn't be able to say "a_squared = 144," because "a * a = 144" isn't valid.

That doesn't work for me. Do I need to upgrade to the experimental version of bB:

 

New build: Improved if-then

 

Looks like it's still buggy, though.

Link to comment
Share on other sites

That doesn't work for me. Do I need to upgrade to the experimental version of bB:

 

New build: Improved if-then

 

Looks like it's still buggy, though.

It works for me, but you have to be sure to include div_mul.asm, since it uses multiplication. Try this example:

 

  include div_mul.asm

  def a_squared = a * a

  a = 11
  if a_squared = 144 then COLUBK = $C6 else COLUBK = $44

loop

  drawscreen
  goto loop

When you compile and run it, the screen should be red, because 11 squared is 121, not 144. Then change it to "a = 12" and recompile it. Now the screen should be green.

 

Michael

Link to comment
Share on other sites

Oh, I wasn't using include div_mul.asm. I should have thought of that. Thanks.

 

As it is, it still doesn't work for me. Whatever version of bB I'm using doesn't allow any math in an if then, even a + a.

 

I'm using the official version from the web site with all of the added updates here:

 

http://www.atariage.com/forums/topic/133529-updated-bb-files-to-download/

 

 

Now that it has include div_mul.asm, this adapted version will work:

 

   include div_mul.asm

  def a_squared = a * a

  a = 11 : temp5 = a_squared
  if temp5 = 144 then COLUBK = $C6 else COLUBK = $44

loop

  drawscreen
  goto loop

Link to comment
Share on other sites

I'm using the official version from the web site with all of the added updates here:

Oh, okay, then there was some kind of if-then bug in the version you're using. What's the date on the 2600basic.exe file you're using?

 

Michael

Here's the date: 5/26/2007

 

 

It finally sank in that you created a pretty good description of def here:

 

http://www.atariage.com/forums/topic/166856-minor-request-for-the-next-version-of-bb/page__p__2062604#entry2062604

 

 

 

I changed it a little and put it on the bB page:

 

http://www.randomterrain.com/atari-2600-memories-batari-basic-commands.html#def

 

 

I still need to add more examples and mention it under the bitop section.

 

 

I can add what you said about macros too:

 

http://www.atariage.com/forums/topic/135778-10-25-08-batari-basic-build/page__view__findpost__p__1638064

 

Do you know the proper place for macros on the bB page? I still don't understand them or exactly why they would be better than using def.

 

 

Speaking of the bB page, it hit me that I probably have Constants in the wrong place. I should probably move it above dim:

 

Variable Testing

 

let (optional)

 

dim

 

def

 

Bit Operations

 

Nybble me this, Batman!

 

Bitwise (Logical) Operators (&, |, ^)

 

Fixed Point Variables

 

Ephemeral Variables and Registers

 

Constants (variables with fixed value)

 

Seem like a good idea?

 

 

Thanks.

Edited by Random Terrain
Link to comment
Share on other sites

Here's the date: 5/26/2007

I think you need the 10/25/2008 or 8/22/2010 version of 2600basic.exe for "def" to work. I don't think it was in the 5/26/2007 version yet.

 

It finally sank in that you created a pretty good description of def here:

 

http://www.atariage.com/forums/topic/166856-minor-request-for-the-next-version-of-bb/page__p__2062604#entry2062604

 

 

 

I changed it a little and put it on the bB page:

 

http://www.randomterrain.com/atari-2600-memories-batari-basic-commands.html#def

 

 

I still need to add more examples and mention it under the dim section.

And you could mention it again under the "Bit Operations" section, too.

 

I can add what you said about macros too:

 

http://www.atariage.com/forums/topic/135778-10-25-08-batari-basic-build/page__view__findpost__p__1638064

 

Do you know the proper place for macros on the bB page? I still don't understand them or exactly why they would be better than using def.

Macros can be a complex topic, and I think they're best left for intermediate-to-advanced programmers, so you might put them after functions but before assembly language.

 

Speaking of the bB page, it hit me that I probably have Constants in the wrong place. I should probably move it above dim:

 

Variable Testing

 

let (optional)

 

dim

 

def

 

Bit Operations

 

Nybble me this, Batman!

 

Bitwise (Logical) Operators (&, |, ^)

 

Fixed Point Variables

 

Ephemeral Variables and Registers

 

Constants (variables with fixed value)

 

Seem like a good idea?

 

 

Thanks.

Constants aren't variables, so I don't think you should call them "variables with fixed values," because to me that really means giving a particular value to a variable and then not changing it at all while the program's running-- like saying "a = 5" and then never changing "a" again. That would waste a variable, whereas defining a constant won't waste a variable. When you define a constant, you're really just giving a symbolic name or label to a number, like "const one = 1." In some cases that doesn't make a lot of sense, because it's easier to just type the number. But constants can be very useful in certain situations, such as getting the lo and hi bytes of something's address in the ROM (such as an sdata table or sprite data table) without having to know ahead of time where it will end up when you compile the program. Since that sort of thing is more likely to be done by intermediate or advanced programmers, I don't think I'd put "const" too far up in the list. On the other hand, you *could* put it before "dim," and then refer to it again in later sections if it's relevant.

 

Michael

  • Like 1
Link to comment
Share on other sites

Do you know the proper place for macros on the bB page? I still don't understand them or exactly why they would be better than using def.

I've been thinking about making some better examples of macros, and showing how you could use "def" and "macro" together to "create your own bB commands" so to speak. So you might want to hold off on adding anything about macros until there are some better examples to show. Earlier tonight I posted some more about macros in another thread, but I think I need to post a new explanation describing them from the ground up.

 

Michael

  • Like 1
Link to comment
Share on other sites

I think you need the 10/25/2008 or 8/22/2010 version of 2600basic.exe for "def" to work. I don't think it was in the 5/26/2007 version yet.

Must have been floating around in there since def is working for me. Do you have any idea how I'd hunt down one of those files? It seems batari hasn't made a sticky thread for periodic releases.

 

I can't wait until batari gets the bugs out of the latest version so we can all use the same version and won't have to add all kinds of updated files here and there. We'll be back to one download, everything included.

 

 

 

And you could mention it again under the "Bit Operations" section, too.

I edited my post too late. I meant the bitop section, not the dim section.

 

 

 

Constants aren't variables, so I don't think you should call them "variables with fixed values," because to me that really means giving a particular value to a variable and then not changing it at all while the program's running-- like saying "a = 5" and then never changing "a" again. That would waste a variable, whereas defining a constant won't waste a variable. When you define a constant, you're really just giving a symbolic name or label to a number, like "const one = 1." In some cases that doesn't make a lot of sense, because it's easier to just type the number. But constants can be very useful in certain situations, such as getting the lo and hi bytes of something's address in the ROM (such as an sdata table or sprite data table) without having to know ahead of time where it will end up when you compile the program. Since that sort of thing is more likely to be done by intermediate or advanced programmers, I don't think I'd put "const" too far up in the list. On the other hand, you *could* put it before "dim," and then refer to it again in later sections if it's relevant.

Thanks.

 

 

 

Do you know the proper place for macros on the bB page? I still don't understand them or exactly why they would be better than using def.

I've been thinking about making some better examples of macros, and showing how you could use "def" and "macro" together to "create your own bB commands" so to speak. So you might want to hold off on adding anything about macros until there are some better examples to show. Earlier tonight I posted some more about macros in another thread, but I think I need to post a new explanation describing them from the ground up.

Thanks. I'll be ready to add it to the page whenever you are.

Link to comment
Share on other sites

I think you need the 10/25/2008 or 8/22/2010 version of 2600basic.exe for "def" to work. I don't think it was in the 5/26/2007 version yet.

Must have been floating around in there since def is working for me. Do you have any idea how I'd hunt down one of those files? It seems batari hasn't made a sticky thread for periodic releases.

 

I can't wait until batari gets the bugs out of the latest version so we can all use the same version and won't have to add all kinds of updated files here and there. We'll be back to one download, everything included.

 

Look what I found while looking for something else:

 

I'm gonna go with def foo=bar, as it's consistent with other bB syntax.

 

Actually, I added the feature last night. It operates as just a simple replacement, and I've already started using it for redefining bits (def BOSSLEVEL=level{7} was the first test, and it worked!)

 

This brought up another idea. In C, #define can also take arguments, e.g. #define foo(a)=(bar+a)/2. It's kind of like a macro but limited to a single line. So I'd say to keep define for simple replacements and also add a macro command that can use arguments and multiple lines, e.g.:

 

  macro foo(bar, baz)
  if bar=baz then baz=baz+1
  if bar<baz then baz=bar
end

 

This will be a little harder and likely won't get done in a single evening...

I must have ended up with the version he was working on at that time.

Link to comment
Share on other sites

I think you need the 10/25/2008 or 8/22/2010 version of 2600basic.exe for "def" to work. I don't think it was in the 5/26/2007 version yet.

Must have been floating around in there since def is working for me. Do you have any idea how I'd hunt down one of those files? It seems batari hasn't made a sticky thread for periodic releases.

The 10/25/2008 build was posted in the "Illegal Character Error in DASM" thread, which turned out to be caused by trying to "dim" a variable name to a bit variable. The 8/22/2010 build was posted in the "Improved if-then" thread (which I think you've already found).

 

Michael

Link to comment
Share on other sites

The 10/25/2008 build was posted in the "Illegal Character Error in DASM" thread, which turned out to be caused by trying to "dim" a variable name to a bit variable.

Well, stuff a million dollars in my pocket, I've already been in that thread 2 or 3 times in the past couple of days:

 

http://www.atariage.com/forums/topic/133522-illegal-character-error-in-dasm/

 

Did you notice I can be a little [very] slow sometimes [most of the time]? :D

 

I guess I was waiting for a version to follow in a new thread that wasn't a use-at-your-own-risk, may-melt-your-computer-and-rape-your-cat release.

 

Thanks. I'll download it now and see if anything explodes.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...