A dangerous Teensy 3.6 quirk

Status
Not open for further replies.

Bill Greiman

Well-known member
This is not a fault in the K66 chip but a problem in using all of SRAM as one contiguous block.

Here is a quirk common to various Cortex-M4 devices including Teensy 3.6/3.5. For critical systems care in SRAM use is required.

Many Cortex-M4 chips have several blocks of SRAM that appear as a large contiguous block.

Here is a quote from the data sheet for K66 processors.

This device contains SRAM tightly coupled to the ARM Cortex-M4 core. The on-chip SRAM is split into SRAM_L and SRAM_U regions where the SRAM_L and SRAM_U ranges form a contiguous block in the memory map anchored at address 0x2000_0000. As such:

• SRAM_L is anchored to 0x1FFF_FFFF and occupies the space before this ending address.

• SRAM_U is anchored to 0x2000_0000 and occupies the space after this beginning address.

SRAM_L is 64 KB, SRAM_U is 192 KB.

NOTE:

Misaligned accesses across the 0x2000_0000 boundary are not supported in the ARM Cortex-M4 architecture.
The CPU does not fault for misaligned access at the boundary and the Cortex-M4 spec only says the access will be unpredictable.

So if you cross the boundary with a misaligned access, strange things happen. I may do that in the SDIO driver, depending on file position and user buffer alignment.

Even memcpy() fails for a misaligned copy across the boundary!

Here is an example.
Code:
uint32_t buf[4];

void setup() {
  Serial.begin(9600);
  
  // Fill memory with aligned access.
  uint32_t *p = (uint32_t*)0X20000000;
  for (int i = -2; i <= 2; i++) {
    p[i] = 0X87654321;
  }
  // Check memory with aligned access.
  Serial.println("aligned print");
  
  for (int i = -2; i < 2; i++) {
    Serial.print((uint32_t)&p[i], HEX);
    Serial.print(' ');
    Serial.println(p[i], HEX);
  }
  // Show misaligned access fails.
  Serial.println("\nmisaligned print");
  
  for (int i = -8; i <= 4; i++) {
    uint32_t* m = (uint32_t*)(0x20000000 + i);
    Serial.print((uint32_t)m, HEX);
    Serial.print(' ');
    Serial.println(*m, HEX);
  }
  // Misaligned memcpy fails at boundary.   
  Serial.println("\nmemcpy");
  
  memcpy(buf, (uint32_t*)(0X20000000 -7), 16);
  for (int i = 0; i < 4; i++) {
    Serial.println(buf[i], HEX);
  }
}

void loop() {
}

Output from the program:

aligned print
1FFFFFF8 87654321
1FFFFFFC 87654321
20000000 87654321
20000004 87654321

misaligned print
1FFFFFF8 87654321
1FFFFFF9 21876543
1FFFFFFA 43218765
1FFFFFFB 65432187
1FFFFFFC 87654321
1FFFFFFD 876543 <-- should be 21876543
1FFFFFFE 8765 <-- should be 43218765
1FFFFFFF 3000087 <-- should be 65432187
20000000 87654321
20000001 21876543
20000002 43218765
20000003 65432187
20000004 87654321

memcpy
21876543
876543 <-- should be 21876543
21876543
21876543
 
Last edited:
16 / 32-bit writes to 0x1FFFFFFF do result in a hard fault (reads don't). Teensy 3.0, 3.1, 3.2 have the same issue:
https://forum.pjrc.com/threads/25256-Teensy-3-hard-fault-due-to-SRAM_L-and-SRAM_U-boundary

I see thought was given to a ld script that made sure the boundary was not crossed by user structures. That's done in several or the RTOSs I use.

There are lots of cases where you need to copy or access unaligned data to an aligned buffer. DMA with the SDHC controller requires aligned data and even if the user buffer is aligned, a unaligned file position requires unaligned access to bytes in the buffer. I copy data from memory to the SDHC FIFO with unaligned access to SRAM.

When you write to a file in this case you don't get a fault.
 
Last edited:
However, my take is you are lying to the compiler with the explicit cast of an unaligned value to an uint32_t pointer. The compiler is free to (and will) assume that if you converted something to an aligned pointer and then loaded from it or stored to it, that it can use an aligned store/load instruction.

Code:
  for (int i = -8; i <= 4; i++) {
    uint32_t* m = (uint32_t*)(0x20000000 + i);                [COLOR="#FF0000"]// this creates an unaligned pointer[/COLOR]
    Serial.print((uint32_t)m, HEX);
    Serial.print(' ');
    Serial.println(*m, HEX);                                            [COLOR="#FF0000"]// and this dereferences the pointer[/COLOR]
  }

and here if you had not explicitly cast the address to an aligned pointer, the compiler would not have optimized the memcpy to do just loads and stores, instead of calling the function.

Code:
  memcpy(buf, (uint32_t*)(0X20000000 -7), 16);          [COLOR="#FF0000"]// this creates an aligned pointer, which malloc then optimizes[/COLOR]

Quoting from the C99 standard:

A pointer to void shall have the same representation and alignment requirements as a pointer to a character type. Similarly, pointers to qualified or unqualified versions of compatible types shall have the same representation and alignment requirements.28) All pointers to structure types shall have the same representation and alignment requirements as each other. All pointers to union types shall have the same representation and alignment requirements as each other. Pointers to other types need not have the same representation or alignment requirements.

In the signature line of Henry Spencer (ihnp4!utzoo!henry) from USENET in the 1980's and 1990's:

If you lie to the compiler, it will get its revenge.​
 
Last edited:
...time to repeat my wish for a second LED which indicates hardfaults....
This could give a hint, at least, for this and other issues. Different "blinks" could indicate the type of fault...
 
However, my take is you are lying to the compiler with the explicit cast of an unaligned value to an uint32_t pointer.
That's not required. With the default Teensyduino compiler settings, unaligned access is considered supported and used by GCC:

Code:
void __attribute__((noinline)) testMemcpy(char* ptr) {
    uint16_t v = 0x4142;
    memcpy(ptr, &v, 2);
}

No alignment check is performed and an unconditional 16-bit write is used.

Code:
0000046c <testMemcpy(char*)>:
     46c:	b082      	sub	sp, #8
     46e:	ab02      	add	r3, sp, #8
     470:	f244 1242 	movw	r2, #16706	; 0x4142
     474:	f823 2d02 	strh.w	r2, [r3, #-2]!
     478:	881b      	ldrh	r3, [r3, #0]
     47a:	8003      	strh	r3, [r0, #0]
     47c:	b002      	add	sp, #8
     47e:	4770      	bx	lr
 
If you lie to the compiler, it will get its revenge.

I am using a Cortex-CM4 processor so I am allowed to use unaligned pointers. I will suffer performance problems.

The compiler was told Cortex-CM4 supported unaligned access as follows:

The Cortex-M4 processor supports ARMv7 unaligned accesses, and performs all accesses as single, unaligned accesses. They are converted into two or more aligned accesses by the DCode and System bus interfaces.

Unaligned accesses that cross memory map boundaries are architecturally Unpredictable. The processor behavior is boundary dependent.

The lie is the compiler was not told about the memory map boundary. It generates code that works with unaligned access except at the 0x20000000 boundary.

It's amazing how much a compiler does to make programming easier when you don't lie.
 
A better example. only char pointers.

Code:
char src[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
char buf[30];

void setup() {
  Serial.begin(9600);
  char* p = (char*)(0X20000000-8);
  memcpy(p, src, sizeof(src));
  Serial.println(src);
  memcpy(buf, p, sizeof(src));
  Serial.println(buf);
  // Next memcpy fails!
  memcpy(buf, p+1, sizeof(src) -1);
  Serial.println(buf);
}
void loop() {
}

output:
ABCDEFGHIJKLMNOPQRSTUVWXYZ
ABCDEFGHIJKLMNOPQRSTUVWXYZ
BCDEFGH <-- failure

If you move the location to 0x20000000 instead of 0x20000000-8 you get what you expect.
ABCDEFGHIJKLMNOPQRSTUVWXYZ
ABCDEFGHIJKLMNOPQRSTUVWXYZ
BCDEFGHIJKLMNOPQRSTUVWXYZ

The distressing thing is failure without a fault exception. The original program gives fault exceptions on a Cortex-M0+ processor for every unaligned access.
 
Last edited:
I guess, splitting the RAM would prevent the use of large arrays. Many many existing sketches (at least the most of mine) will not work anymore.
Are the mentioned rare problems worth the split ? For me: No, please don't do that.
But some kind of indication would be *really* great.
 
Last edited:
Are the mentioned rare problems worth the split ?
If you write to a file, even with an aligned buffer that spans the split, there is a huge chance of an error.

This takes some explanation. Unless all previous writes were a multiple of four bytes in size, the file positioned will likely be unaligned. This means you move a number of bytes to the sector cache that is not a multiple of four with memcpy which is OK. The remaining data in the buffer is on an unaligned boundary and a memcpy() or other operation to deal with it will fail at the boundary.

I have a number of STM32 boards with this problem. I use ChibiOS/RT with these and the author, Giovanni Di Sirio, has by default used separate sections for each block of RAM with this problem. He defines a section with all contiguous blocks but trying to use "__attribute__ ((section(".ram_section")))" to place variables doesn't really work.

Fortunately it's easy to use your own ld script with ChibiOS/RT.
 
Last edited:
I don't remember any problem of this kind in this forum.
But perhaps Paul could add such a section with fixed address in the upper region, so one could use it, if desired.
Or he can provide an additional, optional linkferfile(?)
 
The distressing thing is failure without a fault exception. The original program gives fault exceptions on a Cortex-M0+ processor for every unaligned access.
For M4 processors you can set Configuration and Control Register to enable hard faults on unaligned memory access:
Code:
char src[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
char buf[30];


void hard_fault_isr() {
  digitalWrite(LED_BUILTIN, HIGH);
  Serial.println("\nhardfault handler");
  Serial.print("SCB_CFSR: ");
  Serial.println(SCB_CFSR, BIN);
  while(1) {
    if (SIM_SCGC4 & SIM_SCGC4_USBOTG) usb_isr();
  }
}
void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  while (!Serial);
  delay(120);
  SCB_CCR |= 0x08;
  Serial.print("SCB_CCR: ");
  Serial.println(SCB_CCR, BIN);
  Serial.begin(9600);
  char* p = (char*)(0X20000000 - 8);
  memcpy(p, src, sizeof(src));
  Serial.print(src);
  Serial.print(" | SCB_CFSR: ");
  Serial.println(SCB_CFSR, BIN);
  memcpy(buf, p, sizeof(src));
  Serial.print(buf);
  Serial.print(" | SCB_CFSR: ");
  Serial.println(SCB_CFSR, BIN);
  // Next memcpy fails!
  memcpy(buf, p + 1, sizeof(src) - 1);
  Serial.print(buf);
  Serial.print(" | SCB_CFSR: ");
  Serial.println(SCB_CFSR, BIN);
}
void loop() {
}
edit: Though this is for all unaligned memory access probably not useful Teensyduino.
 
Last edited:
I don't remember any problem of this kind in this forum.

The case I had was a write to a file. The write appears to work but the data is corrupt at the 0x20000000 boundary and only if the file position before the write is not a multiple of four. Most users would never guess it was due to an unaligned memory problem.

I tested several other boards that have two or more regions of SRAM and Teensy is the only one that fails with programs like memcpy().

I would need to replace memcpy() in generic FAT and exFAT libraries to fix the problem. Seems like memcpy() and valid C/C++ should work on Teensy3.

Here is a memcpy() example that should work but crashes with an access fault. If it worked, I would not have a problem with the generic FAT/exFAT libraries.
Code:
uint8_t b[16] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
void setup() {
  Serial.begin(9600);
  Serial.println("memcpy test");

  // memcpy(void*, const void*, size_t) should allow any source or destination address in SRAM.
  // Works with 0x1ffffff8, fails with 0x1ffffff9. 
  memcpy((void*)0x1ffffff9, b, sizeof(b));
  Serial.println("Done");
}
void loop() {
}
This works as expected but is very slow.
Code:
void* memcpyFix(void* dst, const void* src, size_t count) {
  uint8_t* d = (uint8_t*)dst;
  const uint8_t* s = (const uint8_t*)src;
  for (size_t i = 0; i < count; i++) {
    d[i] = s[i];
  }
  return dst;
}
Other processors use various tricks to avoid problems. The SAM3X processor on DUE has a 64KB block and a 32KB block. From the data sheet:
SRAM0 is accessible over the system Cortex-M3 bus at address 0x2000 0000 and SRAM1 at address 0x20080000. The user can see the SRAM as contiguous thanks to mirror effect, giving 0x2007 0000 - 0x2008 7FFF forSAM3X/A8.
The Due ld script uses the mirrored area and there is no problem at the mirrored boundary.

The STM32 boards I tried also have no problem at boundaries.
 
This works as expected but is very slow.
Code:
void* memcpyFix(void* dst, const void* src, size_t count) {
  uint8_t* d = (uint8_t*)dst;
  const uint8_t* s = (const uint8_t*)src;
  for (size_t i = 0; i < count; i++) {
    d[i] = s[i];
  }
  return dst;
}
This is bound to fail, depending on the compiler options. GCC recognizes this code as memcpy loop and may even call memcpy when it can track aliasing between src and dst.

\\

There is the "-mno-unaligned-access" option for GCC, which will disable unaligned access. However, the non-inlined Newlib memcpy function is built with unaligned access enabled (and will use it). So the TD toolchain libraries would have to be built with "-mno-unaligned-access" access as well.
 
This is bound to fail, depending on the compiler options. GCC recognizes this code as memcpy loop and may even call memcpy when it can track aliasing between src and dst.

\\

There is the "-mno-unaligned-access" option for GCC, which will disable unaligned access. However, the non-inlined Newlib memcpy function is built with unaligned access enabled (and will use it). So the TD toolchain libraries would have to be built with "-mno-unaligned-access" access as well.

memcpy() does get used in my libraries. I checked the ld map.

I replaced the call to memcpy() in the above example with "memcpyWrap() in it's own .cpp file. and it fails.

Code:
// memcpyWrap.cpp
void* memcpyWrap(void* dst, const void* src, size_t count) {
  return memcpy(dst, src, count);
}
sketch:
Code:
#include "memcpyWrap.h"
uint8_t b[16] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};

void setup() {
  Serial.begin(9600);
  Serial.println("memcpy test");
  // Works with 0x1ffffff8, fails with 0x1ffffff1.
  memcpyWrap((void*)0x1ffffff9, b, sizeof(b));
  Serial.println("Done");
}
void loop() {
}

Here is the generated code:
Code:
Disassembly of section .text.setup:

00000000 <setup>:
   0:	4a15      	ldr	r2, [pc, #84]	; (58 <setup+0x58>)
   2:	4916      	ldr	r1, [pc, #88]	; (5c <setup+0x5c>)
   4:	b5f8      	push	{r3, r4, r5, r6, r7, lr}
   6:	6814      	ldr	r4, [r2, #0]
   8:	4d15      	ldr	r5, [pc, #84]	; (60 <setup+0x60>)
   a:	4f16      	ldr	r7, [pc, #88]	; (64 <setup+0x64>)
   c:	f640 10c4 	movw	r0, #2500	; 0x9c4
  10:	780b      	ldrb	r3, [r1, #0]
  12:	b13b      	cbz	r3, 24 <setup+0x24>
  14:	782b      	ldrb	r3, [r5, #0]
  16:	079b      	lsls	r3, r3, #30
  18:	d004      	beq.n	24 <setup+0x24>
  1a:	6813      	ldr	r3, [r2, #0]
  1c:	683e      	ldr	r6, [r7, #0]
  1e:	1b9b      	subs	r3, r3, r6
  20:	2b18      	cmp	r3, #24
  22:	d803      	bhi.n	2c <setup+0x2c>
  24:	6813      	ldr	r3, [r2, #0]
  26:	1b1b      	subs	r3, r3, r4
  28:	4283      	cmp	r3, r0
  2a:	d9f1      	bls.n	10 <setup+0x10>
  2c:	210b      	movs	r1, #11
  2e:	480e      	ldr	r0, [pc, #56]	; (68 <setup+0x68>)
  30:	f7ff fffe 	bl	0 <usb_serial_write>
  34:	480d      	ldr	r0, [pc, #52]	; (38 <_ZN5Print7printlnEv+0x38>)
  36:	f7ff fffe 	bl	0 <_ZN5Print7printlnEv>
  3a:	2210      	movs	r2, #16
  3c:	490c      	ldr	r1, [pc, #48]	; (70 <setup+0x70>)
  3e:	480d      	ldr	r0, [pc, #52]	; (74 <setup+0x74>)
  40:	f7ff fffe 	bl	0 <_Z10memcpyWrapPvPKvj>
  44:	2104      	movs	r1, #4
  46:	480c      	ldr	r0, [pc, #48]	; (78 <setup+0x78>)
  48:	f7ff fffe 	bl	0 <usb_serial_write>
  4c:	4807      	ldr	r0, [pc, #28]	; (6c <setup+0x6c>)
  4e:	e8bd 40f8 	ldmia.w	sp!, {r3, r4, r5, r6, r7, lr}
  52:	f7ff bffe 	b.w	0 <_ZN5Print7printlnEv>
  56:	bf00      	nop
	...
  74:	1ffffff9 	.word	0x1ffffff9
  78:	0000000c 	.word	0x0000000c

Disassembly of section .text.loop:

00000000 <loop>:
   0:	4770      	bx	lr
   2:	bf00      	nop

Code:
Disassembly of section .text._Z10memcpyWrapPvPKvj:

00000000 <_Z10memcpyWrapPvPKvj>:
   0:	f7ff bffe 	b.w	0 <memcpy>

The memcpy used is hardware/teensy/avr/cores/teensy3/memcpy-armv7m.S
Code:
00000000 l    df *ABS*  00000000 memcpy-armv7m.S.o

So the compiler fails with inline replacement and memcpy fails.
 
Last edited:
But perhaps Paul could add such a section with fixed address in the upper region, so one could use it, if desired.
Or he can provide an additional, optional linkferfile(?)

Following Frank's suggestions and discussions earlier on this forum, maybe one has to simply accept that there are two memory regions and tell the linker about it and do not allow allocations across regions.
 
Following Frank's suggestions and discussions earlier on this forum, maybe one has to simply accept that there are two memory regions and tell the linker about it and do not allow allocations across regions.

Yup, or work with fixed addresses to avoid the boundary, or make sure to have aligned access on this boundary only. These are the options, and we have to accept it.
@Paul, If someone is not aware of this (and other pitfalls like not enabled devices), a notification LED would be great ;) making the existing LED dual color (+RED) costs no space but a trace and a few cents...
I know, I'm annoying...
 
...
@Paul, If someone is not aware of this (and other pitfalls like not enabled devices), a notification LED would be great ;) making the existing LED dual color (+RED) costs no space but a trace and a few cents...
I know, I'm annoying...

+1 - Frank is annoying ;) :) - about half of my Teensy time has been keeping up with Frank :) - which is a good thing!

Once a fault occurs - how much of the system is still working? { assuming it is a fault of specific known type }

It would be nice to have a uniform handler that generally works that could be directed to an alert mechanism - blink a (user selected) pin or serial out - dump info to EEPROM? - whatever the usable options would be to based on the expected state of the machine and setup() by the user in advance.

EEPROM idea just occurred. The fault_isr() could be directed to do that to a fixed (user defined) EEPROM area- and in setup() on each start that could be checked and annunciated to the user and selectively reset once seen. On T_3.6 it would have to drop HS_RUN so hopefully that works then?

The user code could call the system to specify the EEPROM area - and provide a pointer to a place/length in RAM to xfer to EEPROM in addition to the fault type. The user code could update this area with meaningful data to dump on the next restart to provide data of machine state prior to fault.

This sounds simple to code - if at all possible? I'd try it but my time is short just now - Frank's Teensy64 is here on my desk and needs to be put in a box among other things.
 
Once a fault occurs - how much of the system is still working? { assuming it is a fault of specific known type }

It would be nice to have a uniform handler that generally works that could be directed to an alert mechanism - blink a (user selected) pin or serial out - dump info to EEPROM? - whatever the usable options would be to based on the expected state of the machine and setup() by the user in advance.

Great Idea, Tim.
I'd try to use the "System Register File" instead.
4.14 System Register file
This device includes a 32-byte register file that is powered in all power modes.
Also, it retains contents during low-voltage detect (LVD) events and is only reset during
a power-on reset.
It is available on all ARM Teensys. The Teensy could display (->USB Serial) the last error after boot, for example... I guess the System Register File is not deleted after reflashing, too ?
(Btw, the Teensy loader needs a simple reset button..just to be a bit more annoying)
But let's not hijack this thread ;)
 
Well Frank - that's annoying :) - but yes GUI Reset is a great feature ( of TyCommander )

If there is an alternate place to save data to be recovered on next restart that works too. Is a 'power on reset' part of each 'power on' that is likely to happen after a hanging fault? EEPROM would allow dumping a USER STRUCT of larger than 32 bytes - or any other system state data on hand - and I could code to EEPROM.
 
EEprom is OK too, if that is possible... but it could delete user-contents. With the "SYSREG", that's unlikely. It really survives any type of reset (but not power-off-on). It's very easy to use: It's an array @ fixed address.
 
@Paul, If someone is not aware of this (and other pitfalls like not enabled devices), a notification LED would be great making the existing LED dual color (+RED) costs no space but a trace and a few cents...

I really like the two color LED idea.

In the future, with more board space, Teensy could have a three color LED and code like the Particle Photon and Electron. Look at what they have done with their LED.

See at the red SOS codes below. I have developed drivers for this device and the three color LED is really helpful.

See animation of the LED here.

Code:
During initial setup of a device these are the usual LED specifications:

    White pulse: Start-up (happens when the Photon is first powered on or when it's reset)
    Flashing blue: Listening Mode, waiting for Wi-Fi credentials
    Flashing green: Connecting to Wi-Fi network
    Flashing cyan: Connecting to Particle Cloud. Connected to the network, but not necessarily the internet yet.
    High-speed flashing cyan: Particle Cloud handshake
    Breathing cyan: Connected to Particle Cloud
    Flashing magenta: Receiving new firmware update over-the-air (OTA)
    Breathing magenta Safe mode, connected to Particle Cloud but user firmware not running


Error Codes

Hopefully, you never see these colors but here are the error LED color codes:

    Flash red twice: Connection failure, no internet connection (technically, can't reach Google)
    Flash red three times: Connection failure, Cloud is unreachable
    Orange flashing: Connection failure, bad handshake

Factory Reset & Bootloader

    Solid white: Factory reset started
    High-speed flashing white: Flashing code from factory reset memory
    Flashing yellow: Bootloader mode, waiting for code over USB or JTAG

Is your Photon blinking red? Oh no!

A pattern of more than 10 red blinks is caused by the firmware crashing.
 
The pattern is 3 short blinks, 3 long blinks, 3 short blinks (SOS pattern), followed 
by a number of blinks that depend on the error, then the SOS pattern again.

Enter safe mode, tweak your firmware and try again!

There are a number of other red blink codes that may be expressed after the SOS blinks:

   1 Hard fault
   2 Non-maskable interrupt fault
   3 Memory Manager fault
   4 Bus fault
   5 Usage fault
   6 Invalid length
   7 Exit
   8 Out of heap memory
   9 SPI over-run
   10 Assertion failure
   11 Invalid case
   12 Pure virtual call
   13 Stack overflow

The device has two switches, reset and setup. Pushing reset restarts the program. Pushing reset while holding setup goes to the loader unless you go to DFU mode by holding it several seconds. The fancy boot loader display magenta and DFU displays flashing yellow.
 
Really interesting topic guys.

I am actually doing a project now where it seems like I have some memory problems about variable declarations and such. I use a Teensy 3.6 where my sketch only uses about 5% of dynamic memory, which is also checked using RamMonitor.h. However, when I add a bit of code to my sketch, not at all related to specific variables, my sketch can respond unexpectedly turning these variables to zero or to weird values.

It seems like this topic could be an explanation for this as well. I am also not so experienced in programming in C++, but from what I read, should I always declare my variables at fixed adresses like you are doing in the upper post Bill? Does anyone have good advice about variable declarations at fixed adresses in general? At least then I know now in which part of SRAM_U I need to declare them and I will be avoiding this problem.

Thanks!
 
...
I have some memory problems about variable declarations and such. I use a Teensy 3.6 where my sketch only uses about 5% of dynamic memory
...

I'd start with confirming the size and use of all the allocations of all arrays and pointers. At 5% on memory that would be under 15KB - so it shouldn't be pushing any boundary and if it happened there it would have shown up more often in the last year+ of BETA test and general use.

If the code is small and uploadable it could be checked for specific understanding/answers.
 
Status
Not open for further replies.
Back
Top