PDA

View Full Version : Assembly confusion



Graham Stabler
10-07-2006, 04:38 PM
I think I saw this mentioned elsewhere but what the heck is going on here:




:IPos add 0, Diff 'Add to encoder position value
wrlong 0, MPosAddr 'Write new position to main memory




I understand add and wrlong but not the use as 0 as a destination, I assume it relates to the label :IPos but I'm not sure how.

I'd also like to know if this is good programming practise or just being fancy :)

Graham

nutson
10-07-2006, 09:24 PM
Lacking an indexed adressing mechanism, propeller assembly makes use of "instruction modification" or "self modifying code" for routines that store / fetch data in tables, stepping through the table by incrementing / decrementing the destination / source address in the instruction that handles them. The instructions MOVS and MOVI are meant to insert a starting address in these instructions. The instruction to be modified is often written as having a source / destination of "0", as it will be modified later anayway.

Nico Hattink

Beau Schwabe
10-07-2006, 11:28 PM
two things... The first argument in the WRLONG instruction is the Value, not the Destination. Second, the Value can not be a literal, it needs to point to a register containing the value you want to pass.
Refer to page 415 of the Propeller manual.

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Beau Schwabe (mailto:bschwabe@parallax.com)

IC Layout Engineer
Parallax, Inc.

Paul Baker
10-08-2006, 03:31 AM
Phil (PhiPi) uses a convention for self modifying place holders that I particularly like, and that is the value 0_0. This evaluates to 0, however it is clear to somone reading the code this is only a place-holder to be modified by a MOVS/MOVD instruction at a later point.

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Paul Baker (mailto:pbaker@parallax.com)
Propeller Applications Engineer
[/url][url=http://www.parallax.com] (http://www.parallax.com)
Parallax, Inc. (http://www.parallax.com)

Mike Green
10-08-2006, 03:34 AM
Another similar convention is to use 0-0, but really anything that stands out and is understood by others is useful.

Graham Stabler
10-08-2006, 06:21 PM
Beau, I just got it the wrong way around.

Could someone explain this specific example rather than the generalities, I don't actually know what an indexed addressing mechanism is and so it is hard to understand what this is trying to achieve and why.

Sorry to act like such a dunce.

Graham

Mike Green
10-08-2006, 09:16 PM
Graham,
Indexed addressing usually is used for array access. Essentially, a computed value is used for an address. This works already on the Propeller for HUB memory access in that the HUB address is taken from a COG memory location and can be computed in any fashion (for RDxxxx and WRxxxx instructions). Similarly, jump addresses are taken from a COG memory location and can be computed, so you can have a table of instructions (like jumps) and a memory location "addr" that has the address of a table entry in it, then do a "JMP addr". What you don't have is the ability to fetch and set data values (like in an array) using a computed address. For that, you have to actually change the instruction to be executed, particularly the source address or destination address field. There are MOVS and MOVD instructions that make this easier. A simple example:



'' To do "repeat j from 9 to 0"
'' " table[j] := j"
mov m,#10
xx mov zz,#table-1
add zz,m
movd yy,zz ' note there's a 1 instruction delay
movd ww,zz ' because the Propeller fetches instructions
yy mov 0-0,m ' ahead of time. The "j" value is kept
ww sub 0-0,#1 ' here in "m" and offset by one partly
djnz m,#xx ' because of the djnz instruction and just
' to demonstrate that it can be done.
'
i res 1
table res 10


Post Edited (Mike Green) : 10/8/2006 1:20:18 PM GMT

parsko
10-09-2006, 04:19 AM
xx mov zz,#table-1




Mike, this is legal??? One can subtract in a mov command?

Mike Green
10-09-2006, 04:27 AM
This is a simple move of a literal (immediate) value to a cog memory location. It just happens that the literal value is a constant expression "table - 1" which happens to be the address of the location called "table" minus one (to compensate for using a counter starting at one). Any instruction source or destination field including immediate values can be a constant expression of any complexity because the compiler is computing the value at "compile time". After the program is compiled, this is just a binary number like the rest of the instruction.

Graham Stabler
10-09-2006, 08:06 AM
OK I think I now get it, you have 10 longs reserved for your array but you can't just write table like you might in spin.

So you take the address of the table which is its name, add an offset to access the element you are interested in and by using the command movd write that into the destination part of the command with label yy, replacing the 0-0 that acted as a place holder. When that command is run it has the correct address of the element of the array.

The main thing I wasn't understanding was the self modifying code remarks made earlier, I now understand that movd acts only on the destination and likewise Movs acts on the source. Looking at your code at first I wondered why you didn't just write mov zz,m and then I realized it would just overwrite zz and not use the address it holds.

Graham

p.s. Now I get it Nico's explanation makes perfect sense.

Jim C
10-11-2006, 03:43 AM
I have come across another little twist about indirect addressing and arrays when using assembly. If there are more than one parameter to pass to or from a new cog they would be passed via an array, with the address of first element being passed as a parameter to the cog. See example below, drawn from the fullduplex object:

VAR
long rx_head
long rx_tail
long tx_head
long tx_tail
long rx_pin


cognew(@entry,@rx_head)' 'pass address of rx_head: first element of array
entry mov t1,par 'mov address of rx_head to t1
add t1,#4 << 2 'shifting 4 two bits to left yields 16. T1 now holds address of rx_pin: 5th element of array
rdlong t2,t1 'read rx_pin value from that 5th address, into t2

The trick that was not clear to me at first was that to get to the next array element, the cog has to add 4. This is because the hub keeps track of things as bytes, so to get to the next long, you need to advance 4.

Jim C

Beau Schwabe
10-11-2006, 04:09 AM
Jim C,




VAR
long rx_head
long rx_tail
long tx_head
long tx_tail
long rx_pin

cognew(@entry,@rx_head)' 'pass address of rx_head: first element of array

entry mov t1,par
rdlong t2,t1 't2 receives rx_head
add t1,#4 '<--Add offset of 4 for next LONG
rdlong t2,t1 't2 receives rx_tail
add t1,#4 '<--Add offset of 4 for next LONG
rdlong t2,t1 't2 receives tx_head
add t1,#4 '<--Add offset of 4 for next LONG
rdlong t2,t1 't2 receives tx_tail
add t1,#4 '<--Add offset of 4 for next LONG
rdlong t2,t1 't2 receives rx_pin


{
Note:
typically for t2 you would have individual varaibles... something more like...




VAR
long rx_head
long rx_tail
long tx_head
long tx_tail
long rx_pin

cognew(@entry,@rx_head)' 'pass address of rx_head: first element of array

entry mov t1, par
rdlong Arg0, t1 'Arg0 receives rx_head
add t1, #4 '<--Add offset of 4 for next LONG
rdlong Arg1, t1 'Arg1 receives rx_tail
add t1, #4 '<--Add offset of 4 for next LONG
rdlong Arg2, t1 'Arg2 receives tx_head
add t1, #4 '<--Add offset of 4 for next LONG
rdlong Arg3, t1 'Arg3 receives tx_tail
add t1, #4 '<--Add offset of 4 for next LONG
rdlong Arg4, t1 'Arg4 receives rx_pin




unless you define your variables you want to pass as Bytes..





VAR
byte rx_head
byte rx_tail
byte tx_head
byte tx_tail
byte rx_pin

cognew(@entry,@rx_head)' 'pass address of rx_head: first element of array

entry mov t1, par
rdbyte Arg0, t1 'Arg0 receives rx_head
add t1, #1 '<--Add offset of 1 for next BYTE
rdbyte Arg1, t1 'Arg1 receives rx_tail
add t1, #1 '<--Add offset of 1 for next BYTE
rdbyte Arg2, t1 'Arg2 receives tx_head
add t1, #1 '<--Add offset of 1 for next BYTE
rdbyte Arg3, t1 'Arg3 receives tx_tail
add t1, #1 '<--Add offset of 1 for next BYTE
rdbyte Arg4, t1 'Arg4 receives rx_pin





consequently if you assigned a word variable you would add 2 for every subsequent entry and use 'rdword' instead.

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Beau Schwabe (mailto:bschwabe@parallax.com)

IC Layout Engineer
Parallax, Inc.

Post Edited (Beau Schwabe (Parallax)) : 10/10/2006 8:43:13 PM GMT

Mike Green
10-11-2006, 04:12 AM
Beau,
That a good segue into a question about the behavior of rdxxxx and wrxxxx instructions with unaligned values.

What does the Propeller do with a RDLONG or WRLONG instruction with a HUB address that's not a multiple of 4?

Similarly, what does it do with a RDWORD or WRWORD instruction with a HUB address that's not a multiple of 2?

Mike

Beau Schwabe
10-11-2006, 04:29 AM
Good question Mike,

I know that in the Assembly Dispatch program, that I have found very useful for calling Assembly functions from Spin, the following section...




jumps byte 0 '0
byte Command1_ '1
byte Command2_ '2
byte NotUsed_
byte NotUsed_ '─┐
byte NotUsed_ ' │
byte NotUsed_ ' ┣─ Additional functions MUST be in groups of 4-bytes (1 long)
byte NotUsed_ '─┘ With this setup, there is a limit of 256 possible functions.
NotUsed_
jmp #mainloop




...will not compile if the number of "byte" references do not equate to a multiple of one long. So the IDE might be smart enough to figure out the
relative byte offset if you create a problem. Now, that said, if you call a routine that is positioned within an odd byte offset, then all bets are off.
I'm not sure what you would run into.

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Beau Schwabe (mailto:bschwabe@parallax.com)

IC Layout Engineer
Parallax, Inc.

Jim C
10-11-2006, 04:36 AM
Beau:

Thanks for the more complete explanation. I had not seen the rdword and wrword commands before, but seeing now how they work, as well as the byte commands, the whole addressing scheme starts to get clear.

Jim C.

Mike Green
10-11-2006, 04:49 AM
Beau,
It would be nice to have this behavior publically documented. It's clearly something that people are not supposed to do and may not be done the same way in future versions of the Propeller, but it would be nice to know to be able to explain why a program misbehaves in a particular way with this mistake.
Mike

Wurlitzer
07-04-2007, 10:47 PM
Beau Schwabe (Parallax) said...
Jim C,




VAR
long rx_head
long rx_tail
long tx_head
long tx_tail
long rx_pin

cognew(@entry,@rx_head)' 'pass address of rx_head: first element of array

entry mov t1,par
rdlong t2,t1 't2 receives rx_head
add t1,#4 '<--Add offset of 4 for next LONG
rdlong t2,t1 't2 receives rx_tail
add t1,#4 '<--Add offset of 4 for next LONG
rdlong t2,t1 't2 receives tx_head
add t1,#4 '<--Add offset of 4 for next LONG
rdlong t2,t1 't2 receives tx_tail
add t1,#4 '<--Add offset of 4 for next LONG
rdlong t2,t1 't2 receives rx_pin


{
Note:
typically for t2 you would have individual varaibles... something more like...


Ok! I am stuck completely. I have a series of Arrays some Byte arrays and some Longs. If I pass the address of my byte array and attempt to locate the value for array index [0] I find I have to add #2 to the address passed to the assembly program.
VAR
· byte· KBArray[64]
· Long· CurStopWord1[16]'
· Long· CurStopWord2[16]
· Word· OutputWord'
· long· ProcessStop'
· long· cog·························· '· long· StRam
·· long LC
PUB start : okay·
lc:=1
· repeat 16· 'load some logical values into array to find in assembly·routine
··· kbarray[lc] :=lc
··· lc:=lc+1


· kbarray[0]:=222' a unique value to locate in assembly routine·
okay := cog := cognew(@entry, @kbarray) + 1

DAT
············· org
entry···················
····················
············· mov VarStartAddr,par
············· or dira,pinmask
············· shl pinmask,#1
············· or dira,pinmask
············· shl pinmask,#2
············· or dira,pinmask
············· shl pinmask,#3
············· or dira,pinmask
·································
············· add VarStartAddr,#2 'It seems I have·increment by 2 to find the first element of KBArray
············· rdbyte tmp,VarStartAddr· wz············
············· cmp tmp,#222· wz'Z flag set if equal·································
············· if_z· or outa,#1 '···The Z flag is set with these values
·
············

Mike Green
07-04-2007, 11:27 PM
Because KBArray is declared as a byte array, it can be placed at any address in the hub memory. Unfortunately, the address that's passed to the assembly routine can only be a long word address. The lower two bits are always forced to zero. The compiler allocates space for the long word values first, then the word values, then the byte values. You have a single word value so the byte values start at a word boundary that's not a long word boundary (+2). The +2 offset gets truncated by the cognew().

One solution is to define KBArray as long[ 16 ] and to refer to its elements in your Spin code as KBArray.byte[ lc ]. Another solution is to pass the address of a long word that you set to the actual address of KBArray like:


VAR long kbaddress
kbaddress := @KBArray
cognew(@entry,@kbaddress)
DAT
entry mov VarStartAddr,par
rdlong VarStartAddr,VarStartAddr

Wurlitzer
07-04-2007, 11:48 PM
Thank you very much Mike! That makes perfect sense!
Craig

Wurlitzer
07-07-2007, 04:36 AM
Well thanks to Mike G. and others, I have my 1st assembly routine running perfectly. It screams! It is writing and reading bunches of data from HUB RAM and processing all that data properly.

Now what I need to do is to launch 3 other pure assembly routines. I attempted to do the following to call the 2nd assembly program which was simply created, right or wrong, as a 2nd DAT statement in the same file as the 1st assembly routine. While there was no error generated, it is obvious that the 2nd routine is not running. (for this test I simply flash a unique LED in each routine)

From my spin program:
okay := cog := cognew(@entry, @MasterArray) + 1 'This called routine runs fine as evident by a LED flash routine.

okay := cog := cognew(@OutputDriverCog, @MasterArray) + 1 '** NOTE: the same LED flash routine (with different PIN) does not run

Obviously this is not the correct way to do this so my question is 2 fold.
1) Can a single Spin program call multiple assembly programs? How?
2) Can or should the 2nd assembly routine be located in the original spin file or as a seperate file and what is the best configuration.

NOTE: I am not trying to create multiple instances of an object as all the assembly routines will be doing completely different tasks.

Thanks in advance, Craig

Mike Green
07-07-2007, 04:49 AM
You need to have an "ORG 0" statement before OutputDriverCog since each cog program needs to be assembled starting at the first long word of the cog. The first address in the COGNEW is the address of the long word to be copied to location zero of the new cog. You don't need two DAT statements although you can certainly have them. Basically you have


DAT
ORG 0
entry ' stuff for 1st cog

' more stuff for 1st cog

ORG 0
OutputDriverCog ' stuff for 2nd cog

' more stuff for 2nd cog

Wurlitzer
07-07-2007, 04:56 AM
Boy Mike you are good and fast. Thanks for your help!

Craig

Wurlitzer
07-07-2007, 05:24 AM
Mike, I have the ORG in the locations you described but the 2nd routine still does not run. What are the requirements of local variables in the 2 assembly routines as if I try to use the same name local variable the compiler complains of the duplicate name. By requirements I mean are they simply placed at the end of the 2nd routine?

I have remarked out all the other code in both assembly routines with just the following remaining

DAT
org
entry
mov VarStartAddr,par 'Retrieves RAM starting location for HUB variables



{========= SET PINS TO OUTPUT MODE =======================}
mov tmp,#1
shl tmp,#18
mov pinmask,#1
:PinDirLoop 'set pins to output mode
cmp tmp,pinmask wz, nr 'tmp=loop counter. Set Z flag if values are the same
or dira,pinmask 'Set this pin to output mode
shl pinmask,#1 'Shift bit left one position
if_nz jmp #:PinDirLoop 'If all pins required not set, continue to loop
{-----------------------------------------------------------------------------------------------------}


:TestLoop
waitcnt time,_off_time ' wait for delay to pass
xor outa ,#2 ' toggle the pin
waitcnt time,_on_time ' wait for delay to pass
xor outa,#2 ' toggle
jmp #:TestLoop

In the 2nd assembly routine I simply added a 2 to the loop labels "TestLoop2" and used xor outa, #1

Just to verify the outputs I swapped the outa, #pin numbers and yes the 1st routine still works.

Wurlitzer
07-07-2007, 05:44 AM
Hold the presses. Mike, it does work. The problem was my very first statement in the 2nd assembly routine used a variable name from the first routine yet the compiler did not, nor does it seem to ever complain about using a variable declared in the first routine.

The compiler only seems to complain about a duplicate decaration.

Anyway, thanks again.

deSilva
07-08-2007, 10:33 AM
@Wurlitzer:
(1) How should the compiler know which part of code is assumed to be put into which COG?
(2) Do you preset TIME correcly for the first WAITCNT ? There is a general misunderstanding of how WAITCNT works. It does NOT wait until TIME + DELTA http://forums.parallax.com/images/smilies/smile.gif

@Mike, Beau
(4) Mike asked yesterday what will happen when RDLONG A, B finds an "odd" value in B. IMO Beau did not answer that question; I think however the answer is: It ignores the LSBs
(5) As padding does happen in the DAT section (it has to, as there is no re-allocation as in the VAR section), I think when mixing COG code with BYTE or WORD definitions, there will be no problems

Wurlitzer
07-09-2007, 02:09 AM
deSilva:
1) Other than 2 or more ORGs I don't know how the compiler determines which part of the code is placed in a given COG. I had assumed a 2nd DAT statement would be the indicator for the compiler. In any event I had used 2 DAT and 2 ORG statements but the problem was I did not use a unique name for the variable which received the PAR from the SPIN routine. I thought the compiler would complain if I had used a variable in both DAT routines but it did not. It only complained when I used the same name in the xxxx RES 1 variable declarations.

2) Both of the assembly routines do have a long delay befor the LEDs start to flash at the start but I knew what caused that but I was not concerned as at this point. I wanted to determine the best way to call multiple assembly routines. The actual routines work fine.

That brings up a point where I am still confused. I can now create and run multiple assembly routines in multiple COGs but only if included in a single SPIN file. While this will work fine, I would like, if possible, to have separate files with Assembly ONLY code and call those routines from a single SPIN program. As of yet my little mind has not seen the light.

It would be much easier to write and edit if each assembly routine were in its own file. I understand how to use Objects in Spin but again, I only wanted a single Spin routine to launch 1 or more pure assembly routines.

I also want to understand the indexing through a table described earlier in this thread. My current application would work slick running a table of 96 longs. It needs to take an index received via a serial port and based on that index value, retrieve the proper bit pattern from a table in as efficient manner as possible.

Beau Schwabe
07-09-2007, 04:50 AM
deSilva,

"...Mike asked yesterday what will happen when RDLONG A, B finds an "odd" value in B. IMO Beau did not answer that question..."

Actually the question was asked on October 10th 2006, and I did reply on that same day... "...if you call a routine that is positioned within an odd byte offset, then all bets are off.
I'm not sure what you would run into."



In answer to your question.....assume you are referring to two LONGs (8 total BYTES)

If you increment the BYTE pointer every 4 Bytes, then the allignment is correct
bbbbbbbb bbbbbbbb bbbbbbbb bbbbbbbb bbbbbbbb bbbbbbbb bbbbbbbb bbbbbbbb
       
{All of this is the first LONG} {All of this is the second LONG}

Here if you increment the BYTE pointer every 3 Bytes, then your data is corrupted
bbbbbbbb bbbbbbbb bbbbbbbb bbbbbbbb bbbbbbbb bbbbbbbb bbbbbbbb
      
{All of this is the first LONG}   
   
{All of this is the second LONG}

Data overlaps here


Now if you define two variables as a long, and then you look past the end of the long declaration, you start dipping into other variables (words or bytes) or actual
program space. This program space actually defines the END of the Propeller program and variable space, but it is NOT blank... If you look at the HEX view after
pressing F8, the terminating double long looks like this.... FF FF F9 FF FF FF F9 FF

example:




VAR
long Temp1
long Temp2
word Temp3
byte Temp4
byte Temp5

PUB start
cognew(@entry,@Temp1)' 'pass address of Temp1: first element of array

DAT
entry mov t1,par
rdlong t2,t1 't2 receives Temp1
add t1,#4 '<--Add offset of 4 for next LONG
rdlong t2,t1 't2 receives Temp2
add t1,#4 '<--Add offset of 4 for next LONG
rdlong t2,t1 't2 receives Temp3,Temp4, and Temp5

t1 res 1
t2 res 1



▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Beau Schwabe (mailto:bschwabe@parallax.com)

IC Layout Engineer
Parallax, Inc.

Post Edited (Beau Schwabe (Parallax)) : 7/9/2007 3:53:56 AM GMT

ericball
07-10-2007, 01:19 AM
deSilva said...
(1) How should the compiler know which part of code is assumed to be put into which COG?

COGINIT / COGNEW simply copies Address.long[0] through Address.long[495] to COGRAM and then starts executing at COGRAM[0].· (Yes, even if your PASM routine is only 10 instructions, 496 longs are copied.)· The ORG allows the compiler to translate any labels in the code to COGRAM addresses.
·

Fred Hawkins
07-10-2007, 12:47 PM
I don't understand the line at napshr. Can someone explain generally, and specifically the #18/16/13?



' Nap
'
nap rdlong t,#0 'get clkfreq
napshr shr t,#18/16/13 'shr scales time



(fragment from end of Keyboard.spin -- lines 432 - 435)

mirror
07-10-2007, 01:51 PM
Fred Hawkins said...
I don't understand the line at napshr. Can someone explain generally, and specifically the #18/16/13?



' Nap
'
nap rdlong t,#0 'get clkfreq
napshr shr t,#18/16/13 'shr scales time



(fragment from end of Keyboard.spin -- lines 432 - 435)
This is a little bit of the wonder of Propeller. The #18/16/13 is essentially a
comment, which warns you that this line of code is modified elsewhere in the
assembly code. Look for the other references to napshr, they're the ones that
are actually modifying this line of code.



·

deSilva
07-10-2007, 08:20 PM
... and every author invents his own idioms. You can be sure, that when something "senseless" is stated in an address field it is a hint that it will be patched.

Ex.:
0 highly improbably address value, as the first instruction is located here
0-0 absolutely redundant because 0-0 == 0
#18/16/13 makes no sense, as yields 0; wants to say: "Will be patched with 18 or 16 or 13 respectively"

Edit:
RDLONG .., #0
might also be confusing, though is well commented http://forums.parallax.com/images/smilies/smile.gif

Ref. to the manual "....There are two values stored in the initialization area that might be of interest to your program:
a long at $0000 contains the initial master clock frequency, in Hertz, and a byte following it
at $0004 contains the initial value written into the CLK register. These two values can be
read/written using their physical addresses (LONG[$0] and BYTE[$4])"

Post Edited (deSilva) : 7/10/2007 12:29:34 PM GMT

Fred Hawkins
07-10-2007, 09:46 PM
deSilva said...
... and every author invents his own idioms. You can be sure, that when something "senseless" is stated in an address field it is a hint that it will be patched.

Ex.:
0 highly improbably address value, as the first instruction is located here
0-0 absolutely redundant because 0-0 == 0
#18/16/13 makes no sense, as yields 0; wants to say: "Will be patched with 18 or 16 or 13 respectively"

Edit:
RDLONG .., #0
might also be confusing, though is well commented http://forums.parallax.com/images/smilies/smile.gif

Ref. to the manual "....There are two values stored in the initialization area that might be of interest to your program:
a long at $0000 contains the initial master clock frequency, in Hertz, and a byte following it
at $0004 contains the initial value written into the CLK register. These two values can be
read/written using their physical addresses (LONG[$0] and BYTE[$4])"
My two cents: the label ought to include the initials SM_ -- self-modifying. That way it stands out both
at the actual line and, importantly, as a destination elsewhere. So in this case, SM_napshr.

I do wish the author·had been·a little more verbose.·His·comment didn't help.

deSilva
07-11-2007, 07:15 AM
Fred Hawkins said...
I do wish the author had been a little more verbose. His comment didn't help.

He probably concentrated on writing an efficient program rather than a tutorial on what Propeller assembly is all about http://forums.parallax.com/images/smilies/smile.gif

CardboardGuru
07-11-2007, 07:47 AM
<I>He probably concentrated on writing an efficient program rather than a tutorial on what Propeller assembly is all about </I>

The way it was written is confusing even to those who understand Prop ASM self modifying code. This is where coding standards help. It wouldn't have taken any more time to put #0-0 rather than #18/16/13.

(That's an eg, as we don't actually have any coding standards for the Prop.)