Description
For the following ObjC implementation:
@implementation ContainerObjCWraper
+ (instancetype)newWithContainer:(Container)context { /* ... */}
@end
Any ObjC method call to this new method would pass the Container argument with a LLVM IR byval attribute, and the LLVM function define for this method includes the byval attribute:
define internal noundef i8* @"\01+[ContainerObjCWraper newWithContainer:]"
(i8* noundef %self, i8* noundef %_cmd, %struct.Container* noundef byval(%struct.Container) align 8 %context) #1 {
The Swift ClangImporter in the presence of C++-Interop does not include this byval (byval(%struct.Container)
) and at the callsite instead produces:
call %1* bitcast (void ()* @objc_msgSend to %1* (i8*, i8*, %struct.Container*)*)(i8* %52, i8* %51, %struct.Container* nonnull %2)
where I think it could possibly instead produce:
call %1* bitcast (void ()* @objc_msgSend to %1* (i8*, i8*, %struct.Container*)*)(i8* %52, i8* %51,
%struct.Container* nonnull byval(%struct.Container) %2)
I am still trying to understand why, but I believe it is because in the importer the ObjCMethodDecl is handled in a way where it is assumed that the argument is ABIArgInfo::Direct versus an ABIArgInfo::Indirect with a byval flag.
I have some example code that is affected by this behavior where on X86_64 (found with iOS Simulator) passing the mentioned C++ struct type by value results an a corruption in the parameter passing (often the resulting value is passed with some 8-16 bytes offset incorrectly.
The example code is as follows:
Implementation:
// CXX.mm
#include <CXX.h>
@implementation ContainerObjCWraper
+ (instancetype)newWithContainer:(Container)context {
context.dump();
return nil;
}
@end
Header:
// CXX.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
struct Container {
NSString *_Nullable a; NSString *_Nullable b; NSString *_Nullable c;
NSString *_Nullable d; NSString *_Nullable e; NSString *_Nullable f;
void dump() const {
NSLog(@"a: %@", a == nil ? @"" : a); NSLog(@"b: %@", b == nil ? @"" : b);
NSLog(@"c: %@", c == nil ? @"" : c); NSLog(@"d: %@", d == nil ? @"" : d);
NSLog(@"e: %@", e == nil ? @"" : e); NSLog(@"f: %@", f == nil ? @"" : f);
}
static Container
build(NSString *_Nullable a, NSString *_Nullable b, NSString *_Nullable c,
NSString *_Nullable d, NSString *_Nullable e, NSString *_Nullable f) {
return { .a = a, .b = b, .c = c, .d = d, .e = e, .f = f };
}
};
@interface ContainerObjCWraper : NSObject
+ (instancetype)newWithContainer:(Container)context;
@end
NS_ASSUME_NONNULL_END
Swift:
// main.swift
import Foundation
import CXX
print("building context...")
let context = Container.build(nil, nil, "Hello World", nil, nil, nil)
print("dump context before pass by value to objc new...")
context.dump()
print("dump context after pass by value to objc new...")
_ = ContainerObjCWraper.new(with: context)
print("DONE")
Module Map (module.modulemap):
module CXX {
header "CXX.h"
}
Compile and running should result in:
$ ./run.sh ~/local/S/build/RelWithDebInfo/BinaryCache/toolchain/bin/
Using /Users/plotfi/local/S/build/RelWithDebInfo/BinaryCache/toolchain/bin/ as toolchain...
building context...
dump context before pass by value to objc new...
2022-11-03 22:32:18.538 test-x86_64-apple-macosx12.0.0.exe[74361:3430041] a:
2022-11-03 22:32:18.538 test-x86_64-apple-macosx12.0.0.exe[74361:3430041] b:
2022-11-03 22:32:18.538 test-x86_64-apple-macosx12.0.0.exe[74361:3430041] c: Hello World
2022-11-03 22:32:18.538 test-x86_64-apple-macosx12.0.0.exe[74361:3430041] d:
2022-11-03 22:32:18.538 test-x86_64-apple-macosx12.0.0.exe[74361:3430041] e:
2022-11-03 22:32:18.538 test-x86_64-apple-macosx12.0.0.exe[74361:3430041] f:
dump context after pass by value to objc new...
2022-11-03 22:32:18.538 test-x86_64-apple-macosx12.0.0.exe[74361:3430041] a:
2022-11-03 22:32:18.538 test-x86_64-apple-macosx12.0.0.exe[74361:3430041] b:
2022-11-03 22:32:18.538 test-x86_64-apple-macosx12.0.0.exe[74361:3430041] c: Hello World
2022-11-03 22:32:18.538 test-x86_64-apple-macosx12.0.0.exe[74361:3430041] d:
2022-11-03 22:32:18.538 test-x86_64-apple-macosx12.0.0.exe[74361:3430041] e:
2022-11-03 22:32:18.538 test-x86_64-apple-macosx12.0.0.exe[74361:3430041] f:
DONE
But instead result in the following which is offset and contains garbage (or possibly even a segfault):
$ ./run.sh ~/local/S/build/RelWithDebInfo/BinaryCache/toolchain/bin/
Using /Users/plotfi/local/S/build/RelWithDebInfo/BinaryCache/toolchain/bin/ as toolchain...
building context...
dump context before pass by value to objc new...
2022-11-03 22:30:49.318 test-x86_64-apple-macosx12.0.0.exe[74177:3428829] a:
2022-11-03 22:30:49.319 test-x86_64-apple-macosx12.0.0.exe[74177:3428829] b:
2022-11-03 22:30:49.319 test-x86_64-apple-macosx12.0.0.exe[74177:3428829] c: Hello World
2022-11-03 22:30:49.319 test-x86_64-apple-macosx12.0.0.exe[74177:3428829] d:
2022-11-03 22:30:49.319 test-x86_64-apple-macosx12.0.0.exe[74177:3428829] e:
2022-11-03 22:30:49.319 test-x86_64-apple-macosx12.0.0.exe[74177:3428829] f:
dump context after pass by value to objc new...
2022-11-03 22:30:49.319 test-x86_64-apple-macosx12.0.0.exe[74177:3428829] a: KDº@[Þ*
2022-11-03 22:30:49.319 test-x86_64-apple-macosx12.0.0.exe[74177:3428829] b:
2022-11-03 22:30:49.319 test-x86_64-apple-macosx12.0.0.exe[74177:3428829] c:
2022-11-03 22:30:49.319 test-x86_64-apple-macosx12.0.0.exe[74177:3428829] d: Hello World
2022-11-03 22:30:49.319 test-x86_64-apple-macosx12.0.0.exe[74177:3428829] e:
2022-11-03 22:30:49.319 test-x86_64-apple-macosx12.0.0.exe[74177:3428829] f:
DONE
I have attached a zip file that includes a run.sh file and the above sources to build and run all of this with a ./run.sh /path/to/toolchain/bin