Page 1 of 1

NSLog doesn't work properly with arm compiler

Posted: Mon Oct 31, 2022 1:53 pm
by deseven
The following code works fine with x86_64 compiler:

Code: Select all

Macro CocoaAllocString(String)
  CocoaMessage(0, 0, "NSString stringWithBytes:", @String, "length:", StringByteLength(String), "encoding:", #NSUTF16LittleEndianStringEncoding)
EndMacro

ImportC ""
  NSLog(format, message)
EndImport

Define format = CocoaAllocString("Output: %S")
Define message.s = "Test: " + FormatDate("%HH:%II:%SS", Date())

NSLog(format, @message)
With x86_64 compiler it produces correct log string "Output: Test: %current_time%", with arm compiler it produces "Output: %garbage%" instead.

To see system logs, open Console app, press "Start" on top, input "PureBasic" in the search field, then run the code above.

PB 6.00 LTS @ macOS 11.6

More info in this topic.

Re: NSLog doesn't work properly with arm compiler

Posted: Sat Nov 05, 2022 11:34 pm
by dibor
Confirm.
I got on M1 Max with arm64 compiler - Output: (null)
When run x64 compiler - got - Output: Test: 00:31:57

PB 6.00 LTS @ macOS 12.6.1

Re: NSLog doesn't work properly with arm compiler

Posted: Fri Mar 10, 2023 1:43 am
by sevny
Image

a macbook air m1 with ventura : tests with the pieces of code of this page

Re: NSLog doesn't work properly with arm compiler

Posted: Fri Mar 10, 2023 5:09 am
by yuki
Not a bug but instead relying on undefined behaviour.

NSLog(NSString *format, ...) is a variadic function but you've defined it as accepting merely two parameters with: NSLog(format.i, message.i).

Because of calling convention differences between Apple's AMD64 and ARM64 platforms, we see the breakage here. It's only a coincidence that it works with AMD64 due to overlap between the typical convention and that used for variadic argument lists.

Potential solutions:
  • Bump a feature request for variadic functions, so the language/compiler can accommodate these as needed.
    • Has been requested a couple times: 1, 2.
    • Would be nice to have, so more FFI cases can be covered.
    • This would be much more work for @Fred, and require a permanent change to PB's syntax.
  • Update the VCall module to handle ARM64 + C-backend.
As a workaround (not a true solution), you can pass sufficient arguments to manually satisfy expected layout:

Code: Select all

Macro CocoaAllocString(String)
  CocoaMessage(0, 0, "NSString stringWithBytes:", @String, "length:", StringByteLength(String), "encoding:", #NSUTF16LittleEndianStringEncoding)
EndMacro

ImportC ""
  NSLog(format, message)
EndImport

Define format = CocoaAllocString("Output: %S")
Define message.s = "Test: " + FormatDate("%HH:%II:%SS", Date())

CompilerIf #PB_Compiler_Processor = #PB_Processor_Arm64
  CallFunctionFast(@NSLog(),
                   format,
                   0,
                   0,
                   0,
                   0,
                   0,
                   0,
                   0,
                   @message)
CompilerElse
  NSLog(format, @message)
CompilerEndIf
Though, a simpler workaround would almost certainly be to disregard the variadic nature entirely, and compose the string to log on PB's end (taking care to replace any "%" with "%%").

Re: NSLog doesn't work properly with arm compiler

Posted: Fri Mar 10, 2023 10:30 am
by deseven
yuki wrote: Fri Mar 10, 2023 5:09 am Not a bug but instead relying on undefined behaviour.
Thank you so much for this great explanation! It makes total sense.

If I understood you correctly we can use something like that and it should work for both compilers without any additional checks:

Code: Select all

Macro CocoaAllocString(String)
  CocoaMessage(0, 0, "NSString stringWithBytes:", @String, "length:", StringByteLength(String), "encoding:", #NSUTF16LittleEndianStringEncoding)
EndMacro

ImportC ""
  NSLog(format)
EndImport

Procedure SystemLog(message.s)
  message = ReplaceString(message,"%","%%")
  Protected format = CocoaAllocString(message)
  NSLog(format)
EndProcedure

SystemLog("test 123%")

Re: NSLog doesn't work properly with arm compiler

Posted: Fri Mar 10, 2023 10:35 am
by yuki
deseven wrote: Fri Mar 10, 2023 10:30 am
yuki wrote: Fri Mar 10, 2023 5:09 am Not a bug but instead relying on undefined behaviour.
Thank you so much for this great explanation! It makes total sense.

If I understood you correctly we can use something like that and it should work for both compilers without any additional checks:

Code: Select all

Macro CocoaAllocString(String)
  CocoaMessage(0, 0, "NSString stringWithBytes:", @String, "length:", StringByteLength(String), "encoding:", #NSUTF16LittleEndianStringEncoding)
EndMacro

ImportC ""
  NSLog(format)
EndImport

Procedure SystemLog(message.s)
  message = ReplaceString(message,"%","%%")
  Protected format = CocoaAllocString(message)
  NSLog(format)
EndProcedure

SystemLog("test 123%")
No problem! And that's correct, though you'll want to make sure you release the intermediary NSString allocated:

Code: Select all

Procedure SystemLog(message.s)
  message = ReplaceString(message,"%","%%")
  Protected format = CocoaAllocString(message)
  NSLog(format)
  CocoaMessage(0, format, "release") ;; ← Add this line to ensure memory is freed. 
EndProcedure

Re: NSLog doesn't work properly with arm compiler

Posted: Fri Mar 10, 2023 10:40 am
by deseven
Ah, right. I always forget to clean stuff created with CocoaMessage :)
Thanks.

Re: NSLog doesn't work properly with arm compiler

Posted: Fri Mar 10, 2023 11:56 am
by yuki
To be fair, with convenience methods like stringWithBytes:length:encoding:, they shouldn't be manually released. At least when using WindowEvent() or WaitWindowEvent() (or otherwise properly managing a release-pool manually).

For library code though, I somewhat prefer to manage the lifecycle myself, that way it works without needing a window/release-pool setup prior. So alloc, init, and release instead (or for strings, CFStringCreateWithCharacters() and CFRelease() are a bit more performant).

Re: NSLog doesn't work properly with arm compiler

Posted: Fri Mar 10, 2023 8:00 pm
by mk-soft

Code: Select all

;- Make CocoaMessage threadsafe

Procedure foo() ; Make Thread
  Protected Pool
   
  Pool = CocoaMessage(0, 0, "NSAutoreleasePool new")
  
  ; Do any
  
  If Pool
    CocoaMessage(0, Pool, "release")
  EndIf
  
EndProcedure

Re: NSLog doesn't work properly with arm compiler

Posted: Mon Mar 13, 2023 1:43 pm
by sevny
Image