discuss-gnustep
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Which ObjC2.0 features are missing in the latest GCC?


From: David Chisnall
Subject: Re: Which ObjC2.0 features are missing in the latest GCC?
Date: Mon, 25 Nov 2019 11:09:49 +0000

On 25 Nov 2019, at 10:18, H. Nikolaus Schaller <hns@goldelico.com> wrote:
> 
> Fred mentioned that it could be possible to define some block wrapper macros 
> if some time is invested.
> It that works out, we do not make our decisions depend on gcc *not* 
> implementing something.

Fred made this claim, but he also added the caveat that it would likely be 
limited to blocks with no captures.  In this, I believe that he’s right.  A 
stateless block is:

 - A function that’s used when you invoke the block.
 - A fixed structure describing the block, its type encoding and a couple of 
other things.

Stateful blocks also add a load of fields on the end that describe how to copy 
and destroy each of the captures.  For blocks to actually be useful, you need 
state.  You can’t, with C macros, implement something that defines a load of 
functions inline.  You probably could create a set of macros that would 
simplify the effort in building the block byval structures, but it’s still 
non-trivial.

Note that it is probably possible (though verbose) to implement this using C++ 
templates (though matching the ABI is tricky).  I think most people reading 
this have no idea how complex the block generation code is.  When I implemented 
Pragmatic Smalltalk, I write compiler support for a subset of the blocks ABI 
and that was a few hundred lines of code for the compiler.  Doing the same in 
metaprogramming (especially with C macros) would be incredibly hard.


Consider this trivial compilation unit:

```
@interface X
- (void)doSomething;
@end

typedef void(^block)(void);

block callMethodOn(id x)
{
        return ^() { [x doSomething]; };
}
```

Now, when I run this through clang, this is what I get out as LLVM IR (eliding 
everything that isn’t directly related to blocks):

```
%struct.__block_descriptor = type { i64, i64 }

$__copy_helper_block_e8_32s = comdat any

$__destroy_helper_block_e8_32s = comdat any


@"__block_descriptor_40_e8_32s_e5_v8\01?0l" = linkonce_odr hidden unnamed_addr 
constant { i64, i64, i8*, i8*, i8*, i8* } { i64 0, i64 40, i8* bitcast (void 
(i8*, i8*)* @__copy_helper_block_e8_32s to i8*), i8* bitcast (void (i8*)* 
@__destroy_helper_block_e8_32s to i8*), i8* getelementptr inbounds ([6 x i8], 
[6 x i8]* @.str, i32 0, i32 0), i8* null }, align 8

; Function Attrs: nounwind uwtable
define dso_local void ()* @callMethodOn(i8*) local_unnamed_addr #0 {
  %2 = alloca <{ i8*, i32, i32, i8*, %struct.__block_descriptor*, i8* }>, align 
8
  %3 = tail call i8* @llvm.objc.retain(i8* %0) #1
  %4 = getelementptr inbounds <{ i8*, i32, i32, i8*, 
%struct.__block_descriptor*, i8* }>, <{ i8*, i32, i32, i8*, 
%struct.__block_descriptor*, i8* }>* %2, i64 0, i32 5
  %5 = getelementptr inbounds <{ i8*, i32, i32, i8*, 
%struct.__block_descriptor*, i8* }>, <{ i8*, i32, i32, i8*, 
%struct.__block_descriptor*, i8* }>* %2, i64 0, i32 0
  store i8* bitcast (i8** @_NSConcreteStackBlock to i8*), i8** %5, align 8
  %6 = getelementptr inbounds <{ i8*, i32, i32, i8*, 
%struct.__block_descriptor*, i8* }>, <{ i8*, i32, i32, i8*, 
%struct.__block_descriptor*, i8* }>* %2, i64 0, i32 1
  store i32 -1040187392, i32* %6, align 8
  %7 = getelementptr inbounds <{ i8*, i32, i32, i8*, 
%struct.__block_descriptor*, i8* }>, <{ i8*, i32, i32, i8*, 
%struct.__block_descriptor*, i8* }>* %2, i64 0, i32 2
  store i32 0, i32* %7, align 4
  %8 = getelementptr inbounds <{ i8*, i32, i32, i8*, 
%struct.__block_descriptor*, i8* }>, <{ i8*, i32, i32, i8*, 
%struct.__block_descriptor*, i8* }>* %2, i64 0, i32 3
  store i8* bitcast (void (i8*)* @__callMethodOn_block_invoke to i8*), i8** %8, 
align 8
  %9 = getelementptr inbounds <{ i8*, i32, i32, i8*, 
%struct.__block_descriptor*, i8* }>, <{ i8*, i32, i32, i8*, 
%struct.__block_descriptor*, i8* }>* %2, i64 0, i32 4
  store %struct.__block_descriptor* bitcast ({ i64, i64, i8*, i8*, i8*, i8* }* 
@"__block_descriptor_40_e8_32s_e5_v8\01?0l" to %struct.__block_descriptor*), 
%struct.__block_descriptor** %9, align 8
  store i8* %0, i8** %4, align 8, !tbaa !2
  %10 = bitcast <{ i8*, i32, i32, i8*, %struct.__block_descriptor*, i8* }>* %2 
to void ()*
  %11 = bitcast void ()* %10 to i8*
  %12 = tail call i8* @llvm.objc.retain(i8* %0) #1
  %13 = call i8* @llvm.objc.retainBlock(i8* nonnull %11) #1, 
!clang.arc.copy_on_escape !5
  %14 = bitcast i8* %13 to void ()*
  %15 = load i8*, i8** %4, align 8
  call void @llvm.objc.release(i8* %15) #1, !clang.imprecise_release !5
  call void @llvm.objc.release(i8* %0) #1, !clang.imprecise_release !5
  %16 = tail call i8* @llvm.objc.autoreleaseReturnValue(i8* %13) #1
  ret void ()* %14
}

; Function Attrs: nounwind
declare i8* @llvm.objc.retain(i8*) #1

; Function Attrs: uwtable
define internal void @__callMethodOn_block_invoke(i8* nocapture readonly) #2 {
  %2 = getelementptr inbounds i8, i8* %0, i64 32
  %3 = bitcast i8* %2 to i8**
  %4 = load i8*, i8** %3, align 8, !tbaa !2
  tail call void bitcast (i8* (i8*, ...)* @objc_msgSend to void (i8*, 
i8*)*)(i8* %4, i8* bitcast ({ i8*, i8* }* 
@".objc_selector_doSomething_v16\010:8" to i8*)), !GNUObjCMessageSend !6, 
!clang.arc.no_objc_arc_exceptions !5
  ret void
}

declare dso_local i8* @objc_msgSend(i8*, ...) local_unnamed_addr

; Function Attrs: uwtable
define linkonce_odr hidden void @__copy_helper_block_e8_32s(i8*, i8*) 
unnamed_addr #2 comdat {
  %3 = getelementptr inbounds i8, i8* %1, i64 32
  %4 = bitcast i8* %3 to i8**
  %5 = load i8*, i8** %4, align 8
  %6 = tail call i8* @llvm.objc.retain(i8* %5) #1
  ret void
}

; Function Attrs: uwtable
define linkonce_odr hidden void @__destroy_helper_block_e8_32s(i8*) 
unnamed_addr #2 comdat {
  %2 = getelementptr inbounds i8, i8* %0, i64 32
  %3 = bitcast i8* %2 to i8**
  %4 = load i8*, i8** %3, align 8
  tail call void @llvm.objc.release(i8* %4) #1, !clang.imprecise_release !5
  ret void
}

```

A few things to notice:

1. This single function generates 4 functions.
2. The copy and dispose helpers have to understand the offset of each of the 
captures.  This is fairly simple in this example, because there’s only one 
capture.
3. To avoid bloating the binaries, the copy and destroy helpers have well-known 
names and COMDATs, so the linker can eliminate duplicates.
4. The memory management around creating the block and its captures is 
non-trivial.  Note that this is compiled with ARC, blocks for Objective-C 
implement a subset of ARC even in non-ARC mode because getting the memory 
management right was considered too hard.

If, every time you want to write something as simple as the original 
Objective-C source, you have to write something as complex as the LLVM output, 
that’s your choice, but it’s definitely not one that I’d make.  

David





reply via email to

[Prev in Thread] Current Thread [Next in Thread]