Skip to content

Commit

Permalink
Added capturing and non-capturing lambda detection, and proper handli…
Browse files Browse the repository at this point in the history
…ng of each.

	Change on 2015/07/17 by kirbs <kirbs@google.com>
-------------
Created by MOE: http://code.google.com/p/moe-java
MOE_MIGRATED_REVID=98504227
  • Loading branch information
sjkirby authored and kstanger committed Jul 29, 2015
1 parent a9ef85f commit f925199
Show file tree
Hide file tree
Showing 15 changed files with 516 additions and 146 deletions.
6 changes: 3 additions & 3 deletions jre_emul/Classes/J2ObjC_source.h
Expand Up @@ -34,10 +34,10 @@ FOUNDATION_EXPORT void JreRelease(id obj);
#endif #endif


// Defined in JreEmulation.m // Defined in JreEmulation.m
FOUNDATION_EXPORT id GetNonCapturingLambda(Class baseClass, NSString * blockClassName, FOUNDATION_EXPORT id GetNonCapturingLambda(Class baseClass, NSString *blockClassName,
SEL methodSelector, id block);
FOUNDATION_EXPORT id GetCapturingLambda(Class baseClass, NSString *blockClassName,
SEL methodSelector, id block); SEL methodSelector, id block);
FOUNDATION_EXPORT id GetCapturingLambda(int argumentCount, Class baseClass,
NSString *blockClassName, SEL methodSelector, id block);


/*! /*!
* Returns correct result when casting a double to an integral type. In C, a * Returns correct result when casting a double to an integral type. In C, a
Expand Down
156 changes: 107 additions & 49 deletions jre_emul/Classes/JreEmulation.m
Expand Up @@ -23,6 +23,7 @@
#import "JreEmulation.h" #import "JreEmulation.h"


#import "IOSClass.h" #import "IOSClass.h"
#import "FastPointerLookup.h"
#import "java/lang/AbstractStringBuilder.h" #import "java/lang/AbstractStringBuilder.h"
#import "java/lang/ClassCastException.h" #import "java/lang/ClassCastException.h"
#import "java/lang/NullPointerException.h" #import "java/lang/NullPointerException.h"
Expand Down Expand Up @@ -89,62 +90,119 @@ id JreRetainVolatile(volatile_id *pVar) {
return [value retain]; return [value retain];
} }


typedef struct {
void *id;
} LambdaHolder;

static void *LambdaLookup(void *ptr) {
LambdaHolder *lh = malloc(sizeof(LambdaHolder));
lh->id = nil;
return lh;
}

static FastPointerLookup_t lambdaLookup = FAST_POINTER_LOOKUP_INIT(&LambdaLookup);

// Method to handle dynamic creation of class wrappers surrounding blocks which come from lambdas // Method to handle dynamic creation of class wrappers surrounding blocks which come from lambdas
// not requiring a capture. // not requiring a capture.
id GetNonCapturingLambda(Class baseClass, NSString *blockClassName, SEL methodSelector, id block) { id GetNonCapturingLambda(Class baseClass, NSString *blockClassName,
// Ideally we would solve this with a singleton instead of a lookup, but Objective-C doesn't have SEL methodSelector, id block) {
// class variables, and we can't define static C variables dynamically.
// TODO(kirbs): Refactor GetNonCapturingLambda for thread safety. Right now dictionary // Relies on lambda names being constant strings with matching pointers for matching names.
// access is not threadsafe, but we will probably be moving away from a dictionary for lambda // This should happen as a clang optimization, as all string constants are kept on the stack for
// lookup and storage. // the program duration.
static NSMutableDictionary *nonCapturingBlockLookup = nil; LambdaHolder *lambdaHolder = FastPointerLookup(&lambdaLookup, (__bridge void*) blockClassName);
static dispatch_once_t once; @synchronized(baseClass) {
dispatch_once(&once, ^{ if (lambdaHolder->id == nil) {
nonCapturingBlockLookup = [[NSMutableDictionary alloc] init]; Class blockClass = objc_allocateClassPair(baseClass, [blockClassName UTF8String], 0);
}); Method method = class_getInstanceMethod(baseClass, methodSelector);
id lambdaObj = [nonCapturingBlockLookup objectForKey:blockClassName]; const char *types = method_getTypeEncoding(method);
if (lambdaObj != nil) { IMP block_implementation = imp_implementationWithBlock(block);
return lambdaObj; class_addMethod(blockClass, methodSelector, block_implementation, types);
objc_registerClassPair(blockClass);
lambdaHolder->id = (void*)[[blockClass alloc] init];
}
} }
Class blockClass = objc_allocateClassPair(baseClass, [blockClassName UTF8String], 0); return (__bridge id) lambdaHolder->id;
Method method = class_getInstanceMethod(baseClass, methodSelector);
const char *types = method_getTypeEncoding(method);
IMP block_implementation = imp_implementationWithBlock(block);
class_addMethod(blockClass, methodSelector, block_implementation, types);
objc_registerClassPair(blockClass);
lambdaObj = [[blockClass alloc] init];
[nonCapturingBlockLookup setObject:lambdaObj forKeyedSubscript:blockClassName];
return lambdaObj;
} }


// Having this hardcoded is definitely not ideal, and I would love a dynamic solution to generated
// capturingLambdaBlockCallers that doesn't rely on packing and unpacking arrays for each lambda.
id capturingLambdaBlockCallers[10];
char *capturingLambdaBlockTypes[10];

// Method to handle dynamic creation of class wrappers surrounding blocks from lambdas requiring // Method to handle dynamic creation of class wrappers surrounding blocks from lambdas requiring
// a capture. // a capture.
id GetCapturingLambda(Class baseClass, NSString *blockClassName, SEL methodSelector, id block) { id GetCapturingLambda(int argumentCount, Class baseClass, NSString *blockClassName,
// Ideally we would solve this by creating a variadic block or method which wraps an SEL methodSelector, id block) {
// underlying block instance variable, and creating instances of classes rather than entirely static dispatch_once_t once;
// new classes. There are workarounds, but as far as I can tell this is going to have the dispatch_once(&once, ^{
// least code footprint, though it may be less performant. char typeHolder[10];
// for(int i = 0; i < 10; i++) {
// Alternatives if (i) {
// Create methods for each selector we need to use from a lambda. typeHolder[i - 1] = '@';
// Comes with code cruft in the generated code. }
// Call all blocks with one array arg, and then unpack the array into variables. typeHolder[i] = 0;
// Comes with cruft in the generated blocks. // strcpy returns the address of the destination string, so we can use one call for writing
// Comes with memory overhead. // and assignment.
// Awkward handling of non-id types. capturingLambdaBlockTypes[i] = strcpy(malloc(i + 1), typeHolder);
// We could lookup the number of arguments in the functional method, and have an array of }
// matching block wrappers.
// Need explicit blocks for all distinct argument lengths we support. capturingLambdaBlockCallers[0] = ^id(id _self){
// Probably more performant than creating a new class for every lambda instance. id (^block)() = objc_getAssociatedObject(_self, (void *) 'b');
static int lambdaCount = 0; return block(_self);
blockClassName = [NSString stringWithFormat:@"%@_%d",blockClassName,lambdaCount++]; };
Class blockClass = objc_allocateClassPair(baseClass, [blockClassName UTF8String], 0); capturingLambdaBlockCallers[1] = ^id(id _self, id a){
IMP block_implementation = imp_implementationWithBlock(block); id (^block)() = objc_getAssociatedObject(_self, (void *) 'b');
Method method = class_getInstanceMethod(baseClass, methodSelector); return block(_self, a);
const char *types = method_getTypeEncoding(method); };
class_addMethod(blockClass, methodSelector, block_implementation, types); capturingLambdaBlockCallers[2] = ^id(id _self, id a, id b){
objc_registerClassPair(blockClass); id (^block)() = objc_getAssociatedObject(_self, (void *) 'b');
return [[blockClass alloc] init]; return block(_self, a, b);
};
capturingLambdaBlockCallers[3] = ^id(id _self, id a, id b, id c){
id (^block)() = objc_getAssociatedObject(_self, (void *) 'b');
return block(_self, a, b, c);
};
capturingLambdaBlockCallers[4] = ^id(id _self, id a, id b, id c, id d){
id (^block)() = objc_getAssociatedObject(_self, (void *) 'b');
return block(_self, a, b, c, d);
};
capturingLambdaBlockCallers[5] = ^id(id _self, id a, id b, id c, id d, id e){
id (^block)() = objc_getAssociatedObject(_self, (void *) 'b');
return block(_self, a, b, c, d, e);
};
capturingLambdaBlockCallers[6] = ^id(id _self, id a, id b, id c, id d, id e, id f){
id (^block)() = objc_getAssociatedObject(_self, (void *) 'b');
return block(_self, a, b, c, d, e, f);
};
capturingLambdaBlockCallers[7] = ^id(id _self, id a, id b, id c, id d, id e, id f, id g){
id (^block)() = objc_getAssociatedObject(_self, (void *) 'b');
return block(_self, a, b, c, d, e, f, g);
};
capturingLambdaBlockCallers[8] = ^id(id _self, id a, id b, id c, id d, id e, id f, id g, id h, id i){
id (^block)() = objc_getAssociatedObject(_self, (void *) 'b');
return block(_self, a, b, c, d, e, f, g, h);
};
capturingLambdaBlockCallers[9] = ^id(id _self, id a, id b, id c, id d, id e, id f, id g, id h, id i){
id (^block)() = objc_getAssociatedObject(_self, (void *) 'b');
return block(_self, a, b, c, d, e, f, g, h, i);
};
});

LambdaHolder *lambdaHolder = FastPointerLookup(&lambdaLookup, (__bridge void*) blockClassName);
@synchronized(baseClass) {
if (lambdaHolder->id == nil) {
Class lambdaClass = objc_allocateClassPair(baseClass, [blockClassName UTF8String], sizeof(id));
IMP block_implementation = imp_implementationWithBlock(capturingLambdaBlockCallers[argumentCount]);
class_addMethod([lambdaClass class], methodSelector, block_implementation,
capturingLambdaBlockTypes[argumentCount]);
objc_registerClassPair(lambdaClass);
lambdaHolder->id = (void*)[lambdaClass class];
}
}
id instance = [[(id) lambdaHolder->id alloc] init];
objc_setAssociatedObject(instance, (void*) 'b', [block copy], OBJC_ASSOCIATION_ASSIGN);
return instance;
} }


// Converts main() arguments into an IOSObjectArray of NSStrings. The first // Converts main() arguments into an IOSObjectArray of NSStrings. The first
Expand Down
127 changes: 89 additions & 38 deletions jre_emul/Tests/com/google/j2objc/java8/LambdaTest.java
Expand Up @@ -16,10 +16,17 @@


import junit.framework.TestCase; import junit.framework.TestCase;


import java.util.ArrayList;
import java.util.List;

interface Function<F, T> { interface Function<F, T> {
T apply(F input); T apply(F input);
} }


interface Callable<T> {
T call();
}

interface Supplier<T> { interface Supplier<T> {
T get(); T get();
} }
Expand All @@ -28,17 +35,17 @@ interface Consumer<R> {
void accept(R input); void accept(R input);
} }


interface FourToOne<F, G, H, I, T> { interface FourToOne<F, G, H, I, R> {
T apply(F f, G g, H h, I i); R apply(F f, G g, H h, I i);
} }


//interface UnaryOperator<T> { interface UnaryOperator<T> {
// public T apply(T t); public T apply(T t);
//} }
//
//interface UOToUO<T> { interface UOToUO<T> {
// UnaryOperator<T> apply(UOToUO<T> x); UnaryOperator<T> apply(UOToUO<T> x);
//} }


/** /**
* Command-line tests for Lambda support. * Command-line tests for Lambda support.
Expand All @@ -49,28 +56,72 @@ public class LambdaTest extends TestCase {


public LambdaTest() {} public LambdaTest() {}


// public static <T> UnaryOperator<T> yComb(UnaryOperator<UnaryOperator<T>> r) { Integer outerX = 0;
// return ((UOToUO<T>) f -> f.apply(f)).apply(f -> r.apply(x -> f.apply(f).apply(x))); int outerY = 0;
// }
// public void testOuterVarCapture() {
// public void testYCombinator() throws Exception { Supplier<Integer> s = () -> outerX;
// UnaryOperator<UnaryOperator<String>> a = (UnaryOperator<String> f) -> { Supplier<Integer> s2 = () -> outerY;
// return (String x) -> { assertEquals((Integer) 0, s.get());
// if (x.equals("1111")) { assertEquals((Integer) 0, s2.get());
// return x; outerX += 42;
// } else { outerY += 42;
// return f.apply(x + "1"); assertEquals((Integer) 42, s.get());
// } assertEquals((Integer) 42, s2.get());
// }; outerX++;
// }; outerY++;
// assertEquals("1111", yComb(a).apply("")); assertEquals((Integer) 43, s.get());
// } assertEquals((Integer) 43, s2.get());

outerX++;
// Function outerF = (x) -> x; outerY++;
// }
// public void testOuterFunctions() throws Exception {
// assertEquals(42, outerF.apply(42)); public void testFunctionArray() throws Exception {
// } List<Function> fs = new ArrayList<Function>();
int[] nums = { 3, 2, 1, 0 };
for (int i : nums) {
fs.add((x) -> i);
}
assertEquals(0, fs.get(3).apply("5"));
assertEquals(1, fs.get(2).apply(new Object()));
assertEquals(2, fs.get(1).apply(new Object()));
assertEquals(3, fs.get(0).apply("4"));
}

public <T> UnaryOperator<T> yComb(UnaryOperator<UnaryOperator<T>> r) {
return ((UOToUO<T>) f -> f.apply(f)).apply(f -> r.apply(x -> f.apply(f).apply(x)));
}

public void testYCombinator() throws Exception {
UnaryOperator<UnaryOperator<String>> a = (UnaryOperator<String> f) -> {
return (x) -> {
if (x.length() == 5) {
return x;
} else {
return f.apply((char) (x.charAt(0) + 1) + x);
}
};
};
assertEquals("edcba", yComb(a).apply("a"));
UnaryOperator<UnaryOperator<Integer>> fibonacci = (UnaryOperator<Integer> f) -> {
return (Integer x) -> {
if (x < 1) {
return 0;
} else if (x == 1) {
return x;
} else {
return f.apply(x - 1) + f.apply(x - 2);
}
};
};
assertEquals((Integer) 55, yComb(fibonacci).apply(10));
}

Function outerF = (x) -> x;

public void testOuterFunctions() throws Exception {
assertEquals(42, outerF.apply(42));
}


static Function staticF = (x) -> x; static Function staticF = (x) -> x;


Expand All @@ -89,8 +140,8 @@ public void testNestedLambdas() throws Exception {
assertEquals(f4.apply("42").apply("43"), Integer.valueOf(42)); assertEquals(f4.apply("42").apply("43"), Integer.valueOf(42));
Function<String, Function<Integer, Integer>> f5 = (x) -> (y) -> Integer.parseInt(x); Function<String, Function<Integer, Integer>> f5 = (x) -> (y) -> Integer.parseInt(x);
assertEquals(f5.apply("42").apply(43), Integer.valueOf(42)); assertEquals(f5.apply("42").apply(43), Integer.valueOf(42));
// Callable<Callable> c2 = () -> () -> 42; Callable<Callable> c2 = () -> () -> 42;
// assertEquals(c2.call().call(), 42); assertEquals(c2.call().call(), 42);
Supplier<Supplier> s2 = () -> () -> 42; Supplier<Supplier> s2 = () -> () -> 42;
assertEquals(s2.get().get(), 42); assertEquals(s2.get().get(), 42);
} }
Expand Down Expand Up @@ -148,9 +199,9 @@ public void testBasicConsumer() throws Exception {
c.accept(42); c.accept(42);
assertEquals(42, ls[0]); assertEquals(42, ls[0]);
} }
}


// public void testBasicCallable() throws Exception { public void testBasicCallable() throws Exception {
// Callable c = () -> 42; Callable c = () -> 42;
// assertEquals(42, c.call()); assertEquals(42, c.call());
// } }
}
Expand Up @@ -598,7 +598,7 @@ public boolean visit(LambdaExpression node) {
sb.print(methodBinding.getName()); sb.print(methodBinding.getName());
sb.print(" ("); sb.print(" (");
boolean delimiterFlag = false; boolean delimiterFlag = false;
for (VariableDeclaration x : node.parameters()) { for (VariableDeclaration x : node.getParameters()) {
IVariableBinding variableBinding = x.getVariableBinding(); IVariableBinding variableBinding = x.getVariableBinding();
if (delimiterFlag) { if (delimiterFlag) {
sb.print(", "); sb.print(", ");
Expand Down

0 comments on commit f925199

Please sign in to comment.