Fixes golang/go#13087 Change-Id: I9d7319d9ed3eee73e6479510911f796ab0607bd2 Reviewed-on: https://go-review.googlesource.com/16452 Reviewed-by: David Crawshaw <crawshaw@golang.org>
528 lines
14 KiB
Objective-C
528 lines
14 KiB
Objective-C
// Copyright 2015 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <Foundation/Foundation.h>
|
|
#include "seq.h"
|
|
#include "_cgo_export.h"
|
|
|
|
#ifdef DEBUG
|
|
#define LOG_DEBUG(...) NSLog(__VA_ARGS__);
|
|
#else
|
|
#define LOG_DEBUG(...) ;
|
|
#endif
|
|
|
|
#define LOG_INFO(...) NSLog(__VA_ARGS__);
|
|
#define LOG_FATAL(...) \
|
|
{ \
|
|
NSLog(__VA_ARGS__); \
|
|
@throw \
|
|
[NSException exceptionWithName:NSInternalInconsistencyException \
|
|
reason:[NSString stringWithFormat:__VA_ARGS__] \
|
|
userInfo:NULL]; \
|
|
}
|
|
|
|
// * Objective-C implementation of a Go interface type
|
|
//
|
|
// For an interface testpkg.I, gobind defines a protocol GoSeqTestpkgI.
|
|
// Reference tracker (tracker) maintains two maps:
|
|
// 1) _refs: objective-C object pointer -> a refnum (starting from 42).
|
|
// 2) _objs: refnum -> RefCounter.
|
|
//
|
|
// Whenever a user's object conforming the protocol is sent to Go (through
|
|
// a function or method that takes I), _refs is consulted to find the refnum
|
|
// of the object. If not found, the refnum is assigned and stored.
|
|
//
|
|
// _objs is also updated so that the RefCounter is incremented and the
|
|
// user's object is pinned.
|
|
//
|
|
// When a Go side needs to call a method of the interface, the Go side
|
|
// notifies the Objective-C side of the object's refnum, and the method code
|
|
// as gobind assigned. Upon receiving the request, Objective-C side looks
|
|
// up the object from _objs map, and looks up the proxy global function
|
|
// registered in 'proxies'. The global function deserializes/serializes
|
|
// the parameters and sends the method to the object.
|
|
//
|
|
// The RefCount counts the references on objective-C objects from Go side,
|
|
// and pins the objective-C objects until there is no more reference from
|
|
// Go side.
|
|
//
|
|
// * Objective-C proxy of a Go object (struct or interface type)
|
|
//
|
|
// For Go type object, a objective-C proxy instance is created whenever
|
|
// the object reference is passed into objective-C.
|
|
|
|
// A simple thread-safe mutable dictionary.
|
|
@interface goSeqDictionary : NSObject {
|
|
}
|
|
@property NSMutableDictionary *dict;
|
|
@end
|
|
|
|
@implementation goSeqDictionary
|
|
|
|
- (id)init {
|
|
if (self = [super init]) {
|
|
_dict = [[NSMutableDictionary alloc] init];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (id)get:(id)key {
|
|
@synchronized(self) {
|
|
return [_dict objectForKey:key];
|
|
}
|
|
}
|
|
|
|
- (void)put:(id)obj withKey:(id)key {
|
|
@synchronized(self) {
|
|
[_dict setObject:obj forKey:key];
|
|
}
|
|
}
|
|
@end
|
|
|
|
// The proxies maps Go interface name (e.g. go.testpkg.I) to the proxy function
|
|
// gobind generates for interfaces defined in a module. The function is
|
|
// registered by calling go_seq_register_proxy from a global contructor funcion.
|
|
static goSeqDictionary *proxies = NULL;
|
|
|
|
void go_seq_register_proxy(const char *descriptor,
|
|
void (*fn)(id, int, GoSeq *, GoSeq *)) {
|
|
if (proxies == NULL) {
|
|
proxies = [[goSeqDictionary alloc] init];
|
|
}
|
|
// Copying moves the block to the heap.
|
|
id block = [^(id obj, int code, GoSeq *in, GoSeq *out) {
|
|
fn(obj, code, in, out);
|
|
} copy];
|
|
|
|
[proxies put:block withKey:[NSString stringWithUTF8String:descriptor]];
|
|
}
|
|
|
|
// RefTracker encapsulates a map of objective-C objects passed to Go and
|
|
// the reference number counter which is incremented whenever an objective-C
|
|
// object that implements a Go interface is created.
|
|
@interface RefTracker : NSObject {
|
|
int32_t _next;
|
|
NSMutableDictionary *_refs; // map: object ptr -> refnum
|
|
NSMutableDictionary *_objs; // map: refnum -> RefCounter*
|
|
}
|
|
|
|
- (id)init;
|
|
|
|
// decrements the counter of the objective-C object with the reference number.
|
|
// This is called whenever a Go proxy to this object is finalized.
|
|
// When the counter reaches 0, the object is removed from the map.
|
|
- (void)dec:(int32_t)refnum;
|
|
|
|
// returns the object of the reference number.
|
|
- (id)get:(int32_t)refnum;
|
|
|
|
// returns the reference number of the object and increments the ref count.
|
|
// This is called whenever an Objective-C object is sent to Go side.
|
|
- (int32_t)assignRefnumAndIncRefcount:(id)obj;
|
|
@end
|
|
|
|
RefTracker *tracker = NULL;
|
|
|
|
// mem_ensure ensures that m has at least size bytes free.
|
|
// If m is NULL, it is created.
|
|
static void mem_ensure(GoSeq *m, uint32_t size) {
|
|
size_t cap = m->cap;
|
|
if (cap > m->off + size) {
|
|
return;
|
|
}
|
|
if (cap == 0) {
|
|
cap = 64;
|
|
}
|
|
while (cap < m->off + size) {
|
|
cap *= 2;
|
|
}
|
|
m->buf = (uint8_t *)realloc((void *)m->buf, cap);
|
|
if (m->buf == NULL) {
|
|
LOG_FATAL(@"mem_ensure realloc failed, off=%zu, size=%u", m->off, size);
|
|
}
|
|
m->cap = cap;
|
|
}
|
|
|
|
static uint32_t align(uint32_t offset, uint32_t alignment) {
|
|
uint32_t pad = offset % alignment;
|
|
if (pad > 0) {
|
|
pad = alignment - pad;
|
|
}
|
|
return pad + offset;
|
|
}
|
|
|
|
static uint8_t *mem_read(GoSeq *m, uint32_t size, uint32_t alignment) {
|
|
if (size == 0) {
|
|
return NULL;
|
|
}
|
|
if (m == NULL) {
|
|
LOG_FATAL(@"mem_read on NULL GoSeq");
|
|
}
|
|
uint32_t offset = align(m->off, alignment);
|
|
|
|
if (m->len - offset < size) {
|
|
LOG_FATAL(@"short read");
|
|
}
|
|
uint8_t *res = m->buf + offset;
|
|
m->off = offset + size;
|
|
return res;
|
|
}
|
|
|
|
static uint8_t *mem_write(GoSeq *m, uint32_t size, uint32_t alignment) {
|
|
if (m->off != m->len) {
|
|
LOG_FATAL(@"write can only append to seq, size: (off=%zu len=%zu, size=%u)",
|
|
m->off, m->len, size);
|
|
}
|
|
uint32_t offset = align(m->off, alignment);
|
|
mem_ensure(m, offset - m->off + size);
|
|
uint8_t *res = m->buf + offset;
|
|
m->off = offset + size;
|
|
m->len = offset + size;
|
|
return res;
|
|
}
|
|
|
|
// extern
|
|
void go_seq_free(GoSeq *m) {
|
|
if (m != NULL) {
|
|
free(m->buf);
|
|
}
|
|
}
|
|
|
|
#define MEM_READ(seq, ty) ((ty *)mem_read(seq, sizeof(ty), sizeof(ty)))
|
|
#define MEM_WRITE(seq, ty) (*(ty *)mem_write(seq, sizeof(ty), sizeof(ty)))
|
|
|
|
int go_seq_readInt(GoSeq *seq) {
|
|
int64_t v = go_seq_readInt64(seq);
|
|
return v; // Assume that Go-side used WriteInt to encode 'int' value.
|
|
}
|
|
|
|
void go_seq_writeInt(GoSeq *seq, int v) { go_seq_writeInt64(seq, v); }
|
|
|
|
BOOL go_seq_readBool(GoSeq *seq) {
|
|
int8_t v = go_seq_readInt8(seq);
|
|
return v ? YES : NO;
|
|
}
|
|
|
|
void go_seq_writeBool(GoSeq *seq, BOOL v) { go_seq_writeInt8(seq, v ? 1 : 0); }
|
|
|
|
int8_t go_seq_readInt8(GoSeq *seq) {
|
|
int8_t *v = MEM_READ(seq, int8_t);
|
|
return v == NULL ? 0 : *v;
|
|
}
|
|
void go_seq_writeInt8(GoSeq *seq, int8_t v) { MEM_WRITE(seq, int8_t) = v; }
|
|
|
|
int16_t go_seq_readInt16(GoSeq *seq) {
|
|
int16_t *v = MEM_READ(seq, int16_t);
|
|
return v == NULL ? 0 : *v;
|
|
}
|
|
void go_seq_writeInt16(GoSeq *seq, int16_t v) { MEM_WRITE(seq, int16_t) = v; }
|
|
|
|
int32_t go_seq_readInt32(GoSeq *seq) {
|
|
int32_t *v = MEM_READ(seq, int32_t);
|
|
return v == NULL ? 0 : *v;
|
|
}
|
|
void go_seq_writeInt32(GoSeq *seq, int32_t v) { MEM_WRITE(seq, int32_t) = v; }
|
|
|
|
int64_t go_seq_readInt64(GoSeq *seq) {
|
|
int64_t *v = MEM_READ(seq, int64_t);
|
|
return v == NULL ? 0 : *v;
|
|
}
|
|
void go_seq_writeInt64(GoSeq *seq, int64_t v) { MEM_WRITE(seq, int64_t) = v; }
|
|
|
|
float go_seq_readFloat32(GoSeq *seq) {
|
|
float *v = MEM_READ(seq, float);
|
|
return v == NULL ? 0 : *v;
|
|
}
|
|
void go_seq_writeFloat32(GoSeq *seq, float v) { MEM_WRITE(seq, float) = v; }
|
|
|
|
double go_seq_readFloat64(GoSeq *seq) {
|
|
double *v = MEM_READ(seq, double);
|
|
return v == NULL ? 0 : *v;
|
|
}
|
|
void go_seq_writeFloat64(GoSeq *seq, double v) { MEM_WRITE(seq, double) = v; }
|
|
|
|
NSString *go_seq_readUTF8(GoSeq *seq) {
|
|
int32_t len = *MEM_READ(seq, int32_t);
|
|
if (len == 0) { // empty string.
|
|
return @"";
|
|
}
|
|
const void *buf = (const void *)mem_read(seq, len, 1);
|
|
return [[NSString alloc] initWithBytes:buf
|
|
length:len
|
|
encoding:NSUTF8StringEncoding];
|
|
}
|
|
|
|
void go_seq_writeUTF8(GoSeq *seq, NSString *s) {
|
|
int32_t len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
|
|
MEM_WRITE(seq, int32_t) = len;
|
|
|
|
if (len == 0 && s.length > 0) {
|
|
LOG_INFO(@"unable to incode an NSString into UTF-8");
|
|
return;
|
|
}
|
|
|
|
char *buf = (char *)mem_write(seq, len, 1);
|
|
NSUInteger used;
|
|
[s getBytes:buf
|
|
maxLength:len
|
|
usedLength:&used
|
|
encoding:NSUTF8StringEncoding
|
|
options:0
|
|
range:NSMakeRange(0, [s length])
|
|
remainingRange:NULL];
|
|
if (used < len) {
|
|
buf[used] = '\0';
|
|
}
|
|
return;
|
|
}
|
|
|
|
NSData *go_seq_readByteArray(GoSeq *seq) {
|
|
int64_t sz = *MEM_READ(seq, int64_t);
|
|
if (sz == 0) {
|
|
return [NSData data];
|
|
}
|
|
// BUG(hyangah): it is possible that *ptr is already GC'd by Go runtime.
|
|
void *ptr = (void *)(*MEM_READ(seq, int64_t));
|
|
return [NSData dataWithBytes:ptr length:sz];
|
|
}
|
|
|
|
void go_seq_writeByteArray(GoSeq *seq, NSData *data) {
|
|
int64_t sz = data.length;
|
|
MEM_WRITE(seq, int64_t) = sz;
|
|
if (sz == 0) {
|
|
return;
|
|
}
|
|
|
|
int64_t ptr = (int64_t)data.bytes;
|
|
MEM_WRITE(seq, int64_t) = ptr;
|
|
return;
|
|
}
|
|
|
|
typedef void (^proxyFn)(id, int, GoSeq *, GoSeq *);
|
|
|
|
// called from Go when Go tries to access an Objective-C object.
|
|
void go_seq_recv(int32_t refnum, const char *desc, int code, uint8_t *in_ptr,
|
|
size_t in_len, uint8_t **out_ptr, size_t *out_len) {
|
|
if (code == -1) { // special signal from seq.FinalizeRef in Go
|
|
[tracker dec:refnum];
|
|
return;
|
|
}
|
|
GoSeq ins = {};
|
|
ins.buf = in_ptr; // Memory allocated from Go
|
|
ins.off = 0;
|
|
ins.len = in_len;
|
|
ins.cap = in_len;
|
|
id obj = [tracker get:refnum];
|
|
if (obj == NULL) {
|
|
LOG_FATAL(@"invalid object for ref %d", refnum);
|
|
return;
|
|
}
|
|
|
|
NSString *k = [NSString stringWithUTF8String:desc];
|
|
|
|
proxyFn fn = NULL;
|
|
if (proxies != NULL) {
|
|
fn = [proxies get:k];
|
|
}
|
|
if (fn == NULL) {
|
|
LOG_FATAL(@"cannot find a proxy function for %s", desc);
|
|
return;
|
|
}
|
|
GoSeq outs = {};
|
|
fn(obj, code, &ins, &outs);
|
|
|
|
if (out_ptr == NULL) {
|
|
free(outs.buf);
|
|
} else {
|
|
*out_ptr = outs.buf; // Let Go side free this memory
|
|
*out_len = outs.len;
|
|
}
|
|
}
|
|
|
|
void go_seq_send(char *descriptor, int code, GoSeq *req, GoSeq *res) {
|
|
if (descriptor == NULL) {
|
|
LOG_FATAL(@"invalid NULL descriptor");
|
|
}
|
|
uint8_t *req_buf = NULL;
|
|
size_t req_len = 0;
|
|
if (req != NULL) {
|
|
req_buf = req->buf;
|
|
req_len = req->len;
|
|
}
|
|
|
|
uint8_t **res_buf = NULL;
|
|
size_t *res_len = NULL;
|
|
if (res != NULL) {
|
|
res_buf = &res->buf;
|
|
res_len = &res->len;
|
|
}
|
|
|
|
GoString desc;
|
|
desc.p = descriptor;
|
|
desc.n = strlen(descriptor);
|
|
Send(desc, (GoInt)code, req_buf, req_len, res_buf, res_len);
|
|
}
|
|
|
|
#define IS_FROM_GO(refnum) ((refnum) < 0)
|
|
|
|
// init_seq is called when the Go side is initialized.
|
|
void init_seq() { tracker = [[RefTracker alloc] init]; }
|
|
|
|
GoSeqRef *go_seq_readRef(GoSeq *seq) {
|
|
int32_t refnum = go_seq_readInt32(seq);
|
|
if (IS_FROM_GO(refnum)) {
|
|
return [[GoSeqRef alloc] initWithRefnum:refnum obj:NULL];
|
|
}
|
|
return [[GoSeqRef alloc] initWithRefnum:refnum obj:[tracker get:refnum]];
|
|
}
|
|
|
|
// TODO(hyangah): make this go_seq_writeRef(GoSeq *seq, int32_t refnum, id obj)
|
|
// and get read of GoSeqRef.
|
|
void go_seq_writeRef(GoSeq *seq, GoSeqRef *v) {
|
|
int32_t refnum = v.refnum;
|
|
if (!IS_FROM_GO(refnum)) {
|
|
LOG_FATAL(@"go_seq_writeRef on objective-c objects is not permitted");
|
|
}
|
|
go_seq_writeInt32(seq, refnum);
|
|
return;
|
|
}
|
|
|
|
void go_seq_writeObjcRef(GoSeq *seq, id obj) {
|
|
int32_t refnum = [tracker assignRefnumAndIncRefcount:obj];
|
|
go_seq_writeInt32(seq, refnum);
|
|
}
|
|
|
|
@implementation GoSeqRef {
|
|
}
|
|
|
|
- (id)init {
|
|
LOG_FATAL(@"GoSeqRef init is disallowed");
|
|
return nil;
|
|
}
|
|
|
|
// called when an object from Go is passed in.
|
|
- (instancetype)initWithRefnum:(int32_t)refnum obj:(id)obj {
|
|
self = [super init];
|
|
if (self) {
|
|
_refnum = refnum;
|
|
_obj = obj;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc {
|
|
if (IS_FROM_GO(_refnum)) {
|
|
DestroyRef(_refnum);
|
|
}
|
|
}
|
|
@end
|
|
|
|
// RefCounter is a pair of (GoSeqProxy, count). GoSeqProxy has a strong
|
|
// reference to an Objective-C object. The count corresponds to
|
|
// the number of Go proxy objects.
|
|
//
|
|
// RefTracker maintains a map of refnum to RefCounter, for every
|
|
// Objective-C objects passed to Go. This map allows the transact
|
|
// call to relay the method call to the right Objective-C object, and
|
|
// prevents the Objective-C objects from being deallocated
|
|
// while they are still referenced from Go side.
|
|
@interface RefCounter : NSObject {
|
|
}
|
|
@property(strong, readonly) id obj;
|
|
@property int cnt;
|
|
|
|
- (id)initWithObject:(id)obj;
|
|
@end
|
|
|
|
@implementation RefCounter {
|
|
}
|
|
- (id)initWithObject:(id)obj {
|
|
self = [super init];
|
|
if (self) {
|
|
_obj = obj;
|
|
_cnt = 0;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation RefTracker {
|
|
}
|
|
|
|
- (id)init {
|
|
self = [super init];
|
|
if (self) {
|
|
_next = 42;
|
|
_objs = [[NSMutableDictionary alloc] init];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dec:(int32_t)refnum { // called whenever a go proxy object is finalized.
|
|
if (IS_FROM_GO(refnum)) {
|
|
LOG_FATAL(@"dec:invalid refnum for Objective-C objects");
|
|
return;
|
|
}
|
|
@synchronized(self) {
|
|
id key = @(refnum);
|
|
RefCounter *counter = [_objs objectForKey:key];
|
|
if (counter == NULL) {
|
|
LOG_FATAL(@"unknown refnum");
|
|
return;
|
|
}
|
|
int n = counter.cnt;
|
|
if (n <= 0) {
|
|
LOG_FATAL(@"refcount underflow");
|
|
} else if (n == 1) {
|
|
LOG_DEBUG(@"remove the reference %d", refnum);
|
|
NSValue *ptr = [NSValue valueWithPointer:(const void *)(counter.obj)];
|
|
[_refs removeObjectForKey:ptr];
|
|
[_objs removeObjectForKey:key];
|
|
} else {
|
|
counter.cnt = n - 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (id)get:(int32_t)refnum {
|
|
if (IS_FROM_GO(refnum)) {
|
|
LOG_FATAL(@"get:invalid refnum for Objective-C objects");
|
|
return NULL;
|
|
}
|
|
@synchronized(self) {
|
|
RefCounter *counter = _objs[@(refnum)];
|
|
if (counter == NULL) {
|
|
LOG_FATAL(@"unidentified object refnum: %d", refnum);
|
|
return NULL;
|
|
}
|
|
return counter.obj;
|
|
}
|
|
}
|
|
|
|
- (int32_t)assignRefnumAndIncRefcount:(id)obj {
|
|
@synchronized(self) {
|
|
NSValue *ptr = [NSValue valueWithPointer:(const void *)(obj)];
|
|
NSNumber *refnum = [_refs objectForKey:ptr];
|
|
if (refnum == NULL) {
|
|
refnum = @(_next++);
|
|
_refs[ptr] = refnum;
|
|
}
|
|
RefCounter *counter = [_objs objectForKey:refnum];
|
|
if (counter == NULL) {
|
|
counter = [[RefCounter alloc] initWithObject:obj];
|
|
counter.cnt = 1;
|
|
_objs[refnum] = counter;
|
|
} else {
|
|
counter.cnt++;
|
|
}
|
|
return (int32_t)([refnum intValue]);
|
|
}
|
|
}
|
|
|
|
@end
|