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

// Defined in JreEmulation.m
FOUNDATION_EXPORT id GetNonCapturingLambda(Class baseClass, NSString * blockClassName,
SEL methodSelector, id block);
FOUNDATION_EXPORT id GetCapturingLambda(Class baseClass, NSString *blockClassName,
FOUNDATION_EXPORT id GetNonCapturingLambda(Class baseClass, NSString *blockClassName,
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
Expand Down
156 changes: 107 additions & 49 deletions jre_emul/Classes/JreEmulation.m
Expand Up @@ -23,6 +23,7 @@
#import "JreEmulation.h"

#import "IOSClass.h"
#import "FastPointerLookup.h"
#import "java/lang/AbstractStringBuilder.h"
#import "java/lang/ClassCastException.h"
#import "java/lang/NullPointerException.h"
Expand Down Expand Up @@ -89,62 +90,119 @@ id JreRetainVolatile(volatile_id *pVar) {
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
// not requiring a capture.
id GetNonCapturingLambda(Class baseClass, NSString *blockClassName, SEL methodSelector, id block) {
// Ideally we would solve this with a singleton instead of a lookup, but Objective-C doesn't have
// class variables, and we can't define static C variables dynamically.
// TODO(kirbs): Refactor GetNonCapturingLambda for thread safety. Right now dictionary
// access is not threadsafe, but we will probably be moving away from a dictionary for lambda
// lookup and storage.
static NSMutableDictionary *nonCapturingBlockLookup = nil;
static dispatch_once_t once;
dispatch_once(&once, ^{
nonCapturingBlockLookup = [[NSMutableDictionary alloc] init];
});
id lambdaObj = [nonCapturingBlockLookup objectForKey:blockClassName];
if (lambdaObj != nil) {
return lambdaObj;
id GetNonCapturingLambda(Class baseClass, NSString *blockClassName,
SEL methodSelector, id block) {

// Relies on lambda names being constant strings with matching pointers for matching names.
// This should happen as a clang optimization, as all string constants are kept on the stack for
// the program duration.
LambdaHolder *lambdaHolder = FastPointerLookup(&lambdaLookup, (__bridge void*) blockClassName);
@synchronized(baseClass) {
if (lambdaHolder->id == nil) {
Class blockClass = objc_allocateClassPair(baseClass, [blockClassName UTF8String], 0);
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);
lambdaHolder->id = (void*)[[blockClass alloc] init];
}
}
Class blockClass = objc_allocateClassPair(baseClass, [blockClassName UTF8String], 0);
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;
return (__bridge id) lambdaHolder->id;
}

// 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
// a capture.
id GetCapturingLambda(Class baseClass, NSString *blockClassName, SEL methodSelector, id block) {
// Ideally we would solve this by creating a variadic block or method which wraps an
// underlying block instance variable, and creating instances of classes rather than entirely
// new classes. There are workarounds, but as far as I can tell this is going to have the
// least code footprint, though it may be less performant.
//
// Alternatives
// Create methods for each selector we need to use from a lambda.
// Comes with code cruft in the generated code.
// Call all blocks with one array arg, and then unpack the array into variables.
// Comes with cruft in the generated blocks.
// Comes with memory overhead.
// Awkward handling of non-id types.
// 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.
// Probably more performant than creating a new class for every lambda instance.
static int lambdaCount = 0;
blockClassName = [NSString stringWithFormat:@"%@_%d",blockClassName,lambdaCount++];
Class blockClass = objc_allocateClassPair(baseClass, [blockClassName UTF8String], 0);
IMP block_implementation = imp_implementationWithBlock(block);
Method method = class_getInstanceMethod(baseClass, methodSelector);
const char *types = method_getTypeEncoding(method);
class_addMethod(blockClass, methodSelector, block_implementation, types);
objc_registerClassPair(blockClass);
return [[blockClass alloc] init];
id GetCapturingLambda(int argumentCount, Class baseClass, NSString *blockClassName,
SEL methodSelector, id block) {
static dispatch_once_t once;
dispatch_once(&once, ^{
char typeHolder[10];
for(int i = 0; i < 10; i++) {
if (i) {
typeHolder[i - 1] = '@';
}
typeHolder[i] = 0;
// strcpy returns the address of the destination string, so we can use one call for writing
// and assignment.
capturingLambdaBlockTypes[i] = strcpy(malloc(i + 1), typeHolder);
}

capturingLambdaBlockCallers[0] = ^id(id _self){
id (^block)() = objc_getAssociatedObject(_self, (void *) 'b');
return block(_self);
};
capturingLambdaBlockCallers[1] = ^id(id _self, id a){
id (^block)() = objc_getAssociatedObject(_self, (void *) 'b');
return block(_self, a);
};
capturingLambdaBlockCallers[2] = ^id(id _self, id a, id b){
id (^block)() = objc_getAssociatedObject(_self, (void *) 'b');
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
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 java.util.ArrayList;
import java.util.List;

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

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

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

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

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

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

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

public LambdaTest() {}

// public static <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 (String x) -> {
// if (x.equals("1111")) {
// return x;
// } else {
// return f.apply(x + "1");
// }
// };
// };
// assertEquals("1111", yComb(a).apply(""));
// }

// Function outerF = (x) -> x;
//
// public void testOuterFunctions() throws Exception {
// assertEquals(42, outerF.apply(42));
// }
Integer outerX = 0;
int outerY = 0;

public void testOuterVarCapture() {
Supplier<Integer> s = () -> outerX;
Supplier<Integer> s2 = () -> outerY;
assertEquals((Integer) 0, s.get());
assertEquals((Integer) 0, s2.get());
outerX += 42;
outerY += 42;
assertEquals((Integer) 42, s.get());
assertEquals((Integer) 42, s2.get());
outerX++;
outerY++;
assertEquals((Integer) 43, s.get());
assertEquals((Integer) 43, s2.get());
outerX++;
outerY++;
}

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;

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

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

0 comments on commit f925199

Please sign in to comment.