diff --git a/.gitignore b/.gitignore index cef38e8ff..7f6d62960 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,8 @@ mspec/upstream_patches mspec_upstream node_name.inc parse.c +plblockimp/blockimp_ARM* +plblockimp/blockimp_x86* prelude.c rbconfig.rb revision.h diff --git a/lib/mkmf.rb b/lib/mkmf.rb index ff5e9d9d4..d66e55c55 100644 --- a/lib/mkmf.rb +++ b/lib/mkmf.rb @@ -1932,7 +1932,7 @@ def init_mkmf(config = CONFIG) $LDFLAGS = with_config("ldflags", arg_config("LDFLAGS", config["LDFLAGS"])).dup $INCFLAGS = "-I$(arch_hdrdir)" $INCFLAGS << " -I$(hdrdir)/ruby/backward" unless $extmk - $INCFLAGS << " -I$(hdrdir) -I$(srcdir)" + $INCFLAGS << " -I$(hdrdir) -I$(srcdir) -I$(top_srcdir)/plblockimp" $DLDFLAGS = with_config("dldflags", arg_config("DLDFLAGS", config["DLDFLAGS"])).dup $LIBEXT = config['LIBEXT'].dup $OBJEXT = config["OBJEXT"].dup diff --git a/macruby_internal.h b/macruby_internal.h index 9f92f4019..454856ad7 100644 --- a/macruby_internal.h +++ b/macruby_internal.h @@ -13,6 +13,7 @@ extern "C" { #endif #include "ruby.h" +#include "PLBlockIMP.h" #include #include diff --git a/plblockimp/ARM/blockimp_arm.tramp b/plblockimp/ARM/blockimp_arm.tramp new file mode 100644 index 000000000..c708bbfed --- /dev/null +++ b/plblockimp/ARM/blockimp_arm.tramp @@ -0,0 +1,66 @@ +#!/bin/sh + +# ----------------------------------------------------------------------- +# Copyright (c) 2010-2011, Plausible Labs Cooperative, Inc. +# All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# ``Software''), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# ----------------------------------------------------------------------- + +# Architecture definitions. We support either armv6 or armv7 +if [ "${CURRENT_ARCH}" = "armv6" ] || [ "${CURRENT_ARCH}" = "armv7" ]; then + ARCH="${CURRENT_ARCH}" +else + ARCH="" +fi + +PAGE_SIZE="4096" + +# The name of this page +PAGE_NAME=pl_blockimp_table_page + +# Prefix to be placed at the start of the trampoline page +trampoline_prefix () { +asm << 'EOF' + _block_tramp_dispatch: + # trampoline address+8 is in r12 -- calculate our config page address + sub r12, #0x8 + sub r12, #0x1000 + + # Set the 'self' argument as the second argument + mov r1, r0 + + # Load the block pointer as the first argument + ldr r0, [r12] + + # Jump to the block pointer + ldr pc, [r0, #0xc] +EOF +} + +# Generate a single trampoline +trampoline () { +asm << 'EOF' + # Save pc+8, then jump to the shared prefix implementation + mov r12, pc + b _block_tramp_dispatch; +EOF +} \ No newline at end of file diff --git a/plblockimp/ARM/blockimp_arm_stret.tramp b/plblockimp/ARM/blockimp_arm_stret.tramp new file mode 100644 index 000000000..ee30cc8bb --- /dev/null +++ b/plblockimp/ARM/blockimp_arm_stret.tramp @@ -0,0 +1,61 @@ +#!/bin/sh + +# ----------------------------------------------------------------------- +# Copyright (c) 2010-2011, Plausible Labs Cooperative, Inc. +# All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# ``Software''), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# ----------------------------------------------------------------------- + +# Architecture definitions +ARCH="${CURRENT_ARCH}" +PAGE_SIZE="4096" + +# The name of this page +PAGE_NAME=pl_blockimp_table_stret_page + +# Prefix to be placed at the start of the trampoline page +trampoline_prefix () { +asm << 'EOF' + _block_tramp_dispatch: + # trampoline address+8 is in r12 -- calculate our config page address + sub r12, #0x8 + sub r12, #0x1000 + + # Set the 'self' argument as the third argument + mov r2, r1 + + # Load the block pointer as the second argument + ldr r1, [r12] + + # Jump to the block pointer + ldr pc, [r1, #0xc] +EOF +} + +# Generate a single trampoline +trampoline () { +asm << 'EOF' + # Save pc+8, then jump to the shared prefix implementation + mov r12, pc + b _block_tramp_dispatch; +EOF +} \ No newline at end of file diff --git a/plblockimp/LICENSE b/plblockimp/LICENSE new file mode 100644 index 000000000..c39bf1636 --- /dev/null +++ b/plblockimp/LICENSE @@ -0,0 +1,23 @@ +Author: Landon Fuller + +Copyright 2010-2011 Plausible Labs Cooperative, Inc. +All rights reserved. + +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/plblockimp/PLBlockIMP.h b/plblockimp/PLBlockIMP.h new file mode 100644 index 000000000..cb2f0023c --- /dev/null +++ b/plblockimp/PLBlockIMP.h @@ -0,0 +1,28 @@ +/* + * Author: Landon Fuller + * + * Copyright 2010-2011 Plausible Labs Cooperative, Inc. + * All rights reserved. + * + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once +#include "blockimp.h" \ No newline at end of file diff --git a/plblockimp/blockimp.c b/plblockimp/blockimp.c new file mode 100644 index 000000000..2026a9ca8 --- /dev/null +++ b/plblockimp/blockimp.c @@ -0,0 +1,117 @@ +/* + * Author: Landon Fuller + * + * Copyright 2010-2011 Plausible Labs Cooperative, Inc. + * All rights reserved. + * + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "blockimp.h" +#include "blockimp_private.h" + +#include "trampoline_table.h" + +#include +#include + +#pragma mark Trampolines + +/* Global lock for our mutable state. Must be held when accessing the trampoline tables. */ +static pthread_mutex_t blockimp_lock = PTHREAD_MUTEX_INITIALIZER; + +/* Trampoline tables for objc_msgSend_stret() dispatch. */ +static pl_trampoline_table *blockimp_table_stret = NULL; + +/* Trampoline tables for objc_msgSend() dispatch. */ +static pl_trampoline_table *blockimp_table = NULL; + +/** + * + */ +IMP pl_imp_implementationWithBlock (void *block) { +#if SUPPORT_APPLE_FALLBACK + /* Prefer Apple's implementation */ + if (&imp_implementationWithBlock != NULL) + return imp_implementationWithBlock(block); +#endif + + /* Allocate the appropriate trampoline type. */ + pl_trampoline *tramp; + struct Block_layout *bl = block; + if (bl->flags & BLOCK_USE_STRET) { + tramp = pl_trampoline_alloc(&pl_blockimp_table_stret_page_config, &blockimp_lock, &blockimp_table_stret); + } else { + tramp = pl_trampoline_alloc(&pl_blockimp_table_page_config, &blockimp_lock, &blockimp_table); + } + + /* Configure the trampoline */ + void **config = pl_trampoline_data_ptr(tramp->trampoline); + config[0] = Block_copy(block); + config[1] = tramp; + + /* Return the function pointer. */ + return (IMP) tramp->trampoline; +} + +/** + * + */ +void *pl_imp_getBlock(IMP anImp) { +#if SUPPORT_APPLE_FALLBACK + /* Prefer Apple's implementation */ + if (&imp_getBlock != NULL) { + return imp_getBlock(anImp); + } +#endif + + /* Fetch the config data and return the block reference. */ + void **config = pl_trampoline_data_ptr(anImp); + return config[0]; +} + +/** + * + */ +BOOL pl_imp_removeBlock(IMP anImp) { +#if SUPPORT_APPLE_FALLBACK + /* Prefer Apple's implementation */ + if (&imp_removeBlock != NULL) + return imp_removeBlock(anImp); +#endif + + /* Fetch the config data */ + void **config = pl_trampoline_data_ptr(anImp); + struct Block_layout *bl = config[0]; + pl_trampoline *tramp = config[1]; + + /* Drop the trampoline allocation */ + if (bl->flags & BLOCK_USE_STRET) { + pl_trampoline_free(&blockimp_lock, &blockimp_table_stret, tramp); + } else { + pl_trampoline_free(&blockimp_lock, &blockimp_table, tramp); + } + + /* Release the block */ + Block_release(config[0]); + + // TODO - what does this return value mean? + return YES; +} diff --git a/plblockimp/blockimp.h b/plblockimp/blockimp.h new file mode 100644 index 000000000..07b86dc41 --- /dev/null +++ b/plblockimp/blockimp.h @@ -0,0 +1,31 @@ +/* + * Author: Landon Fuller + * + * Copyright 2010-2011 Plausible Labs Cooperative, Inc. + * All rights reserved. + * + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include + +extern IMP pl_imp_implementationWithBlock(void *block); +extern void *pl_imp_getBlock(IMP anImp); +extern BOOL pl_imp_removeBlock(IMP anImp); \ No newline at end of file diff --git a/plblockimp/blockimp_private.h b/plblockimp/blockimp_private.h new file mode 100644 index 000000000..a6fa23b79 --- /dev/null +++ b/plblockimp/blockimp_private.h @@ -0,0 +1,135 @@ +/* + * Private Block ABI Structures + * Originally acquired frm PLBlocks and compiler_rt + * + * Copyright 2008 - 2009 Apple, Inc. + * Copyright 2009 - 2011 Plausible Labs Cooperative, Inc. + * + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#pragma once + +#if defined(__i386__) +# include "blockimp_x86_32.h" +# include "blockimp_x86_32_stret.h" +#elif defined(__x86_64__) +# include "blockimp_x86_64.h" +# include "blockimp_x86_64_stret.h" +#elif defined(__arm__) +# include "blockimp_arm.h" +# include "blockimp_arm_stret.h" +#else +# error Unknown Architecture +#endif + +#pragma mark Fallback Support + +// if 1, we attempt to use Apple's official implementations +#define SUPPORT_APPLE_FALLBACK 0 +#if SUPPORT_APPLE_FALLBACK +extern IMP imp_implementationWithBlock(void *block) WEAK_IMPORT_ATTRIBUTE; +extern void *imp_getBlock(IMP anImp) WEAK_IMPORT_ATTRIBUTE; +extern BOOL imp_removeBlock(IMP anImp) WEAK_IMPORT_ATTRIBUTE; +#endif + +/* + * Block Flags + */ +typedef enum { + /** 16-bit block reference count. */ + BLOCK_REFCOUNT_MASK = (0xffff), + + BLOCK_NEEDS_FREE = (1 << 24), + BLOCK_HAS_COPY_DISPOSE = (1 << 25), + + /** Helpers have C++ code. */ + BLOCK_HAS_CTOR = (1 << 26), + + BLOCK_IS_GC = (1 << 27), + BLOCK_IS_GLOBAL = (1 << 28), + + /** Block returns its aggregate value in memory (ie, the block has a structure return type). */ + BLOCK_USE_STRET = (1 << 29), +} block_flags_t; + + +/* + * Block field flags. + */ +typedef enum { + // see function implementation for a more complete description of these fields and combinations + BLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block, ... + BLOCK_FIELD_IS_BLOCK = 7, // a block variable + BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable + BLOCK_FIELD_IS_WEAK = 16, // declared __weak, only used in byref copy helpers + BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines. +} block_field_flags_t; + +/* + * Block description. + * + * Block descriptions are shared across all instances of a block, and + * provide basic information on the block size, as well as pointers + * to any helper functions necessary to copy or dispose of the block. + */ +struct Block_descriptor { + /** Reserved value */ + unsigned long int reserved; + + /** Total size of the described block, including imported variables. */ + unsigned long int size; + + /** Optional block copy helper. May be NULL. */ + void (*copy)(void *dst, void *src); + + /** Optional block dispose helper. May be NULL. */ + void (*dispose)(void *); +}; + + +/* + * Block instance. + * + * The block layout defines the per-block instance state, which includes + * a reference to the shared block descriptor. + * + * The block's imported variables are allocated following the block + * descriptor member. + */ +struct Block_layout { + /** Pointer to the block's Objective-C class. */ + void *isa; + + /** Block flags. */ + int flags; + + /** Reserved value. */ + int reserved; + + /** Block invocation function. */ + void (*invoke)(void *, ...); + + /** Shared block descriptor. */ + struct Block_descriptor *descriptor; + + // imported variables +}; \ No newline at end of file diff --git a/plblockimp/blockimp_tests.m b/plblockimp/blockimp_tests.m new file mode 100644 index 000000000..7e05ed71f --- /dev/null +++ b/plblockimp/blockimp_tests.m @@ -0,0 +1,148 @@ +/* + * Author: Landon Fuller + * + * Copyright 2010-2011 Plausible Labs Cooperative, Inc. + * All rights reserved. + * + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#import "GTMSenTestCase.h" +#import "blockimp.h" + +@interface BlockIMPTests : SenTestCase @end + +/** + * BlockIMP Tests + */ +@implementation BlockIMPTests + +/** + * Test basic IMP allocation and execution. + */ +- (void) testAllocateIMP { + /* A test block */ + __block BOOL didRun = NO; + void (^Block)(id self) = ^(id blockself) { + didRun = YES; + + STAssertEqualObjects(blockself, self, @"Incorrect 'self' pointer"); + }; + + /* Create the IMP */ + IMP imp = pl_imp_implementationWithBlock(Block); + STAssertTrue(imp != NULL, @"Returned NULL IMP"); + + /* Verify the IMP is valid. */ + void (*ptr)(id self, SEL _cmd) = (void *) imp; + ptr(self, @selector(testAllocateIMP)); + STAssertTrue(didRun, @"Block was not run"); + + /* Clean up */ + pl_imp_removeBlock(imp); +} + +/** + * Test basic stret IMP allocation and execution. + */ +- (void) testAllocateStretIMP { + /* A test block */ + __block BOOL didRun = NO; + NSRange (^Block)(id self) = ^NSRange (id blockself) { + didRun = YES; + + STAssertEqualObjects(blockself, self, @"Incorrect 'self' pointer"); + return NSMakeRange(42, 1); + }; + + /* Create the IMP */ + IMP imp = pl_imp_implementationWithBlock(Block); + STAssertTrue(imp != NULL, @"Returned NULL IMP"); + + /* Verify the IMP is valid. */ + NSRange (*ptr)(id self, SEL _cmd) = (void *) imp; + NSRange result = ptr(self, @selector(testAllocateStretIMP)); + + STAssertTrue(didRun, @"Block was not run"); + STAssertEquals(result.location, (NSUInteger)42, @"Incorrect location"); + STAssertEquals(result.length, (NSUInteger)1, @"Incorrect length"); + + /* Clean up */ + pl_imp_removeBlock(imp); +} + +/** + * Test fetching of the Block ptr from the IMP pointer. + */ +- (void) testGetBlock { + /* A test block */ + void (^Block)(id self) = [[^(id blockself) { + STAssertEqualObjects(blockself, self, @"Incorrect 'self' pointer"); + } copy] autorelease]; + + /* Create the IMP */ + IMP imp = pl_imp_implementationWithBlock(Block); + STAssertTrue(imp != NULL, @"Returned NULL IMP"); + + /* Try to fetch the underlying block */ + void *b = pl_imp_getBlock(imp); + STAssertEquals(b, (void *) Block, @"Did not fetch block"); + + /* Clean up */ + pl_imp_removeBlock(imp); +} + +/** + * Exercise block allocation + */ +- (void) testAllocationExcercise { + /* A test block */ + __block int callCount = 0; + void (^Block)(id self) = [[^(id blockself) { + callCount++; + STAssertEqualObjects(blockself, self, @"Incorrect 'self' pointer"); + } copy] autorelease]; + + /* Use a count larger than what a single page (on any architecture) can likely hold */ + int count=PAGE_SIZE*2; + + /* Generate the IMPs */ + IMP *imps = malloc(sizeof(IMP) * count); + for (int i = 0; i < count; i++) { + imps[i] = pl_imp_implementationWithBlock(Block); + } + + /* Call the IMPs */ + for (int i = 0; i < count; i++) { + void (*ptr)(id self, SEL _cmd) = (void *) imps[i]; + ptr(self, @selector(testAllocationExcercise)); + } + + /* Clean up the IMPs. We do this in reverse to exercise table reordering. */ + for (int i = count-1; i+1 >= 1; i--) { + pl_imp_removeBlock(imps[i]); + } + free(imps); + + /* Verify the result */ + STAssertEquals(callCount, count, @"Call count does not match expected count; not all IMPs were called"); +} + +@end diff --git a/plblockimp/gentramp.sh b/plblockimp/gentramp.sh new file mode 100755 index 000000000..2cc6a427f --- /dev/null +++ b/plblockimp/gentramp.sh @@ -0,0 +1,207 @@ +#!/bin/sh + +# ----------------------------------------------------------------------- +# gentramp.sh - Copyright (c) 2010-2011, Plausible Labs Cooperative, Inc. +# +# Trampoline Page Generator +# Author: Landon Fuller +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# ``Software''), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# ----------------------------------------------------------------------- + +PROGNAME=$0 + +INPUT_FILE_PATH="$1" +INPUT_FILE_BASE="`basename -a $(sed s/\.[^.]*$// <<< $INPUT_FILE_PATH)`" +CURRENT_ARCH="$2" +OUTPUT_DIR="$3" +SRC_C_OUTPUT="${OUTPUT_DIR}/${INPUT_FILE_BASE}_config.c" +SRC_OUTPUT="${OUTPUT_DIR}/${INPUT_FILE_BASE}.s" +HEADER_OUTPUT="${OUTPUT_DIR}/${INPUT_FILE_BASE}.h" + +# Default implementation +trampoline_prefix () { + return 0 +} + +# Import the trampoline definition +. ${INPUT_FILE_PATH} + +check_required () { + local name=$1 + eval "local var=\${$1}" + + if [ -z "${var}" ]; then + echo "Required variable ${name} not defined." + exit 1 + fi +} + +check_required ARCH +check_required PAGE_SIZE +check_required PAGE_NAME + +# Write a header line +header () { + echo "$1" >> "${HEADER_OUTPUT}" +} + +# Write a C source line +src () { + echo "$1" >> "${SRC_C_OUTPUT}" +} + +# Flush the assembler output buffer to disk +ASM_BUFFER="" +asm_flush () { + echo "${ASM_BUFFER}" >> "${SRC_OUTPUT}" + asm_discard +} + +# Write the assembler buffer to disk, but don't discard the contents +asm_write () { + echo "${ASM_BUFFER}" >> "${SRC_OUTPUT}" +} + +# Discard the current assembler output buffer +asm_discard () { + ASM_BUFFER='' + return 0; +} + +# Append data to the assembler output buffer +asm () { + local line="" + while read -r line; do + ASM_BUFFER+=$line + ASM_BUFFER+="\n" + done +} + +# Compute the assembled size of the current assembler buffer +compute_asm_size () { + # Create the temporary assembler file + local output=".globl _byte_count_start\n" + output+="_byte_count_start:\n" + output+="${ASM_BUFFER}" + output+=".globl _byte_count_end\n" + output+="_byte_count_end:\n" + + local tempfile=`mktemp /tmp/as_bytecount.XXXXXXXX` + echo "${output}" | as -arch "${CURRENT_ARCH}" -o "${tempfile}" - + if [ $? != 0 ]; then + echo "Assembling the trampoline failed" + exit 1 + fi + + local byte_size=`nm -t d -P "${tempfile}" | grep ^_byte_count_end | awk '{print $3}'` + rm -f "${tempfile}" + + echo $byte_size +} + + +# Write out the page header +write_page_decl () { + # Calculate the required alignment + local align=`perl -l -e "print log(${PAGE_SIZE})/log(2)"` + asm << EOF + # GENERATED CODE - DO NOT EDIT" + # This file was generated by $PROGNAME on `date` + + # Write out the trampoline table, aligned to the page boundary + .text + .align ${align} + .globl _${PAGE_NAME} + _${PAGE_NAME}: +EOF +} + +main () { + echo '' > "${SRC_OUTPUT}" + echo '' > "${SRC_C_OUTPUT}" + echo '' > "${HEADER_OUTPUT}" + + # Write out the trampoline header file + header "extern void *${PAGE_NAME};" + header "extern struct pl_trampoline_table_config ${PAGE_NAME}_config;" + + # Don't generate the sources for the incorrect arch + if [ "${ARCH}" != "${CURRENT_ARCH}" ]; then + return + fi + + # Determine the trampoline prefix size + trampoline_prefix + local prefix_size=$(compute_asm_size) + asm_discard + + # Compute the size of the remaining code page. + local page_avail=`expr $PAGE_SIZE - $prefix_size` + + # Determine the trampoline size + trampoline + local tramp_size=$(compute_asm_size) + asm_discard + if [ "${tramp_size}" = 0 ]; then + echo "Error occured calculating trampoline size; received size of 0" + exit 1 + fi + + # Compute the number of of available trampolines. + local trampoline_count=`expr $page_avail / $tramp_size` + echo "Prefix size: ${prefix_size}" + echo "Trampoline size: ${tramp_size}" + echo "Trampolines per page: ${trampoline_count}" + + # Write out the page declaration + write_page_decl + asm_flush + + # Write out the prefix + trampoline_prefix + asm_flush + + # Write out the trampolines + trampoline + local i=0 + while [ $i -lt ${trampoline_count} ]; do + asm_write + local i=`expr $i + 1` + done + asm_discard + + # Write out the table configuration + local config_src=`cat << EOF + #include "trampoline_table.h" + + extern void *${PAGE_NAME}; + pl_trampoline_table_config ${PAGE_NAME}_config = { + .trampoline_size = ${tramp_size}, + .page_offset = ${prefix_size}, + .trampoline_count = ${trampoline_count}, + .template_page = &${PAGE_NAME} + }; +EOF` + src "${config_src}" +} + +main diff --git a/plblockimp/trampoline_table.c b/plblockimp/trampoline_table.c new file mode 100644 index 000000000..e41f57f75 --- /dev/null +++ b/plblockimp/trampoline_table.c @@ -0,0 +1,202 @@ +/* + * Author: Landon Fuller + * + * Copyright 2010-2011 Plausible Labs Cooperative, Inc. + * All rights reserved. + * + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +#include "trampoline_table.h" + +#include +#include +#include + + +/* + * Allocate, register, and return a new trampoline table. The trampoline lock must be held by the caller. + * + * @param source_page The source page that will be remapped. + */ +static pl_trampoline_table *pl_trampoline_table_alloc (pl_trampoline_table_config *config) { + pl_trampoline_table *table = NULL; + + /* Loop until we can allocate two contigious pages */ + while (table == NULL) { + vm_address_t data_page = 0x0; + kern_return_t kt; + + /* Try to allocate two pages */ + kt = vm_allocate (mach_task_self(), &data_page, PAGE_SIZE*2, VM_FLAGS_ANYWHERE); + if (kt != KERN_SUCCESS) { + fprintf(stderr, "vm_allocate() failure: %d at %s:%d\n", kt, __FILE__, __LINE__); + break; + } + + /* Now drop the second half of the allocation to make room for the trampoline table */ + vm_address_t trampoline_page = data_page+PAGE_SIZE; + kt = vm_deallocate (mach_task_self(), trampoline_page, PAGE_SIZE); + if (kt != KERN_SUCCESS) { + fprintf(stderr, "vm_deallocate() failure: %d at %s:%d\n", kt, __FILE__, __LINE__); + break; + } + + /* Remap the trampoline table to directly follow the config page */ + vm_prot_t cur_prot; + vm_prot_t max_prot; + + kt = vm_remap (mach_task_self(), &trampoline_page, PAGE_SIZE, 0x0, FALSE, mach_task_self(), (vm_address_t) config->template_page, FALSE, &cur_prot, &max_prot, VM_INHERIT_SHARE); + + /* If we lost access to the destination trampoline page, drop our config allocation mapping and retry */ + if (kt != KERN_SUCCESS) { + /* Log unexpected failures */ + if (kt != KERN_NO_SPACE) { + fprintf(stderr, "vm_remap() failure: %d at %s:%d\n", kt, __FILE__, __LINE__); + } + + vm_deallocate (mach_task_self(), data_page, PAGE_SIZE); + continue; + } + + /* We have valid trampoline and config pages */ + table = calloc (1, sizeof(pl_trampoline_table)); + table->free_count = config->trampoline_count; + table->data_page = data_page; + table->trampoline_page = trampoline_page; + table->config = config; + + /* Create and initialize the free list */ + table->free_list_pool = calloc(config->trampoline_count, sizeof(pl_trampoline)); + + uint16_t i; + for (i = 0; i < table->free_count; i++) { + pl_trampoline *entry = &table->free_list_pool[i]; + entry->table = table; + entry->trampoline = (void *) (table->trampoline_page + (i * config->trampoline_size) + config->page_offset); + + if (i < table->free_count - 1) + entry->next = &table->free_list_pool[i+1]; + } + + table->free_list = table->free_list_pool; + } + + return table; +} + +/** + * Allocate a new trampoline. Returns NULL on error. + * + * @param config The table configuration. This value is owned by the caller, and must survive for the lifetime of the table. + * @param lock The lock to acquire when modifying shared mutable state. + * @param root_table The table from which the entry should be allocated. + */ +pl_trampoline *pl_trampoline_alloc (pl_trampoline_table_config *config, pthread_mutex_t *lock, pl_trampoline_table **table_head) { + pthread_mutex_lock(lock); + + /* Check for an active trampoline table with available entries. */ + pl_trampoline_table *table = *table_head; + if (table == NULL || table->free_list == NULL) { + table = pl_trampoline_table_alloc (config); + if (table == NULL) { + return NULL; + } + + /* Insert the new table at the top of the list */ + table->next = *table_head; + if (table->next != NULL) + table->next->prev = table; + + *table_head = table; + } + + /* Claim the free entry */ + pl_trampoline *entry = (*table_head)->free_list; + (*table_head)->free_list = entry->next; + (*table_head)->free_count--; + entry->next = NULL; + + pthread_mutex_unlock(lock); + + return entry; +} + +/** + * Given a trampoline's code pointer, return its associated data pointer. + */ +void *pl_trampoline_data_ptr (void *code_ptr) { + return (uint8_t *) code_ptr - PAGE_SIZE; +} + +/** + * Deallocate a trampoline and return it to the free list. + * + * @param lock The lock to acquire when modifying shared mutable state. + * @param root_table The root table from which the entry should be deallocated. + * @param tramp The trampoline to deallocate. + */ +void pl_trampoline_free (pthread_mutex_t *lock, pl_trampoline_table **table_head, pl_trampoline *tramp) { + pthread_mutex_lock(lock); + + /* Fetch the table references */ + pl_trampoline_table *table = tramp->table; + + /* Return the entry to the free list */ + tramp->next = table->free_list; + table->free_list = tramp; + table->free_count++; + + /* If all trampolines within this table are free, and at least one other table exists, deallocate + * the table */ + if (table->free_count == table->config->trampoline_count && *table_head != table) { + /* Remove from the list */ + if (table->prev != NULL) + table->prev->next = table->next; + + if (table->next != NULL) + table->next->prev = table->prev; + + /* Deallocate pages */ + kern_return_t kt; + kt = vm_deallocate (mach_task_self(), table->data_page, PAGE_SIZE); + if (kt != KERN_SUCCESS) + fprintf(stderr, "vm_deallocate() failure: %d at %s:%d\n", kt, __FILE__, __LINE__); + + kt = vm_deallocate (mach_task_self(), table->trampoline_page, PAGE_SIZE); + if (kt != KERN_SUCCESS) + fprintf(stderr, "vm_deallocate() failure: %d at %s:%d\n", kt, __FILE__, __LINE__); + + /* Deallocate free list */ + free (table->free_list_pool); + free (table); + } else if (*table_head != table) { + /* Otherwise, bump this table to the top of the list */ + table->prev = NULL; + table->next = *table_head; + if (*table_head != NULL) + (*table_head)->prev = table; + + *table_head = table; + } + + pthread_mutex_unlock (lock); +} diff --git a/plblockimp/trampoline_table.h b/plblockimp/trampoline_table.h new file mode 100644 index 000000000..a3e9e0149 --- /dev/null +++ b/plblockimp/trampoline_table.h @@ -0,0 +1,92 @@ +/* + * Author: Landon Fuller + * + * Copyright 2010-2011 Plausible Labs Cooperative, Inc. + * All rights reserved. + * + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#ifdef PL_BLOCKIMP_PRIVATE + +#include +#include + +typedef struct pl_trampoline_table pl_trampoline_table; +typedef struct pl_trampoline pl_trampoline; + +/* + * Trampoline table configuration + */ +typedef struct pl_trampoline_table_config { + /* The trampoline size */ + uint32_t trampoline_size; + + /* The page offset at which the trampolines are located. */ + uint32_t page_offset; + + /* The number of trampolines allocated per page. */ + uint32_t trampoline_count; + + /** The template code page. */ + void *template_page; +} pl_trampoline_table_config; + +/* + * A double-linked list of trampoline table entries. + */ +struct pl_trampoline_table { + /* Table configuration */ + pl_trampoline_table_config *config; + + /* Contigious writable and executable pages */ + vm_address_t data_page; + vm_address_t trampoline_page; + + /* free list tracking */ + uint16_t free_count; + pl_trampoline *free_list; + pl_trampoline *free_list_pool; + + pl_trampoline_table *prev; + pl_trampoline_table *next; +}; + +/* + * A linked list of trampoline table entries. + */ +struct pl_trampoline { + /* The actual trampoline. */ + void *(*trampoline)(); + + /** The table in which the entry is allocated. */ + pl_trampoline_table *table; + + /* Next entry in the trampoline list. */ + pl_trampoline *next; +}; + +pl_trampoline *pl_trampoline_alloc (pl_trampoline_table_config *config, pthread_mutex_t *lock, pl_trampoline_table **table_head); +void pl_trampoline_free (pthread_mutex_t *lock, pl_trampoline_table **table_head, pl_trampoline *tramp); +void *pl_trampoline_data_ptr (void *code_ptr); + +#endif /* PL_BLOCKIMP_PRIVATE */ diff --git a/plblockimp/x86_32/blockimp_x86_32.tramp b/plblockimp/x86_32/blockimp_x86_32.tramp new file mode 100644 index 000000000..efd2e943c --- /dev/null +++ b/plblockimp/x86_32/blockimp_x86_32.tramp @@ -0,0 +1,67 @@ +#!/bin/sh + +# ----------------------------------------------------------------------- +# Copyright (c) 2010-2011, Plausible Labs Cooperative, Inc. +# All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# ``Software''), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# ----------------------------------------------------------------------- + +# Architecture definitions +ARCH="i386" +PAGE_SIZE="4096" + +# The name of this page +PAGE_NAME=pl_blockimp_table_page + +# Prefix to be placed at the start of the trampoline page +trampoline_prefix () { +asm << 'EOF' + _block_tramp_dispatch: + pop %eax + andl $0xfffffff8, %eax // truncate to the trampoline start (each is 8 bytes) + subl $0x1000, %eax // load the config location + + // Move 'self' to the second parameter, overwriting IMP + movl 0x4(%esp), %ecx + movl %ecx, 0x8(%esp) + + // Load the block reference from the config page, insert as the first parameter + movl (%eax), %eax + movl %eax, 0x4(%esp) + + // Jump to the block fptr + jmp *0xc(%eax) + + .align 3 // align the trampolines at 8 bytes + +EOF +} + +# Generate a single trampoline +trampoline () { +asm << 'EOF' + + # Call into the dispatcher, placing our return address on the stack. + calll _block_tramp_dispatch # 5 bytes + .align 3 // align the trampolines at 8 bytes +EOF +} \ No newline at end of file diff --git a/plblockimp/x86_32/blockimp_x86_32_stret.tramp b/plblockimp/x86_32/blockimp_x86_32_stret.tramp new file mode 100644 index 000000000..28b552f7a --- /dev/null +++ b/plblockimp/x86_32/blockimp_x86_32_stret.tramp @@ -0,0 +1,67 @@ +#!/bin/sh + +# ----------------------------------------------------------------------- +# Copyright (c) 2010-2011, Plausible Labs Cooperative, Inc. +# All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# ``Software''), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# ----------------------------------------------------------------------- + +# Architecture definitions +ARCH="i386" +PAGE_SIZE="4096" + +# The name of this page +PAGE_NAME=pl_blockimp_table_stret_page + +# Prefix to be placed at the start of the trampoline page +trampoline_prefix () { +asm << 'EOF' + _block_tramp_dispatch: + pop %eax + andl $0xfffffff8, %eax // truncate to the trampoline start (each is 8 bytes) + subl $0x1000, %eax // load the config location + + // Move 'self' to the third parameter, overwriting IMP + movl 0x8(%esp), %ecx + movl %ecx, 0xc(%esp) + + // Load the block reference from the config page, insert as the second parameter + movl (%eax), %eax + movl %eax, 0x8(%esp) + + // Jump to the block fptr + jmp *0xc(%eax) + + .align 3 // align the trampolines at 8 bytes +EOF +} + +# Generate a single trampoline +trampoline () { +asm << 'EOF' + + # Call into the dispatcher, placing our return address on the stack. + calll _block_tramp_dispatch # 5 bytes + .align 3 // align the trampolines at 8 bytes + +EOF +} \ No newline at end of file diff --git a/plblockimp/x86_64/blockimp_x86_64.tramp b/plblockimp/x86_64/blockimp_x86_64.tramp new file mode 100644 index 000000000..73da1884a --- /dev/null +++ b/plblockimp/x86_64/blockimp_x86_64.tramp @@ -0,0 +1,62 @@ +#!/bin/sh + +# ----------------------------------------------------------------------- +# Copyright (c) 2010-2011, Plausible Labs Cooperative, Inc. +# All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# ``Software''), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# ----------------------------------------------------------------------- + +# Architecture definitions +ARCH="x86_64" +PAGE_SIZE="4096" + +# The name of this page +PAGE_NAME=pl_blockimp_table_page + +# Prefix to be placed at the start of the trampoline page +trampoline_prefix () { +asm << 'EOF' + _block_tramp_dispatch: + pop %r11 + and $0xfffffffffffffff8, %r11 // truncate to the trampoline start (each is 8 bytes) + sub $0x1000, %r11 // load the config location + + // Move 'self' to the second parameter, overwriting IMP + movq %rdi, %rsi + + // Load the block reference from the config page, and move to the first parameter + movq (%r11), %rdi + + // Jump to the block fptr + jmp *0x10(%rdi) + .align 4 // align the trampolines at 16 bytes (required for config page lookup and sizing) +EOF +} + +# Generate a single trampoline +trampoline () { +asm << 'EOF' + // Call into the dispatcher, placing our return address on the stack. + call _block_tramp_dispatch # 5 bytes + .align 4 // align the trampolines at 16 bytes (required for config page lookup and sizing) +EOF +} \ No newline at end of file diff --git a/plblockimp/x86_64/blockimp_x86_64_stret.tramp b/plblockimp/x86_64/blockimp_x86_64_stret.tramp new file mode 100644 index 000000000..0cc915447 --- /dev/null +++ b/plblockimp/x86_64/blockimp_x86_64_stret.tramp @@ -0,0 +1,62 @@ +#!/bin/sh + +# ----------------------------------------------------------------------- +# Copyright (c) 2010-2011, Plausible Labs Cooperative, Inc. +# All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# ``Software''), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# ----------------------------------------------------------------------- + +# Architecture definitions +ARCH="x86_64" +PAGE_SIZE="4096" + +# The name of this page +PAGE_NAME=pl_blockimp_table_stret_page + +# Prefix to be placed at the start of the trampoline page +trampoline_prefix () { +asm << 'EOF' + _block_tramp_dispatch: + pop %r11 + and $0xfffffffffffffff8, %r11 // truncate to the trampoline start (each is 8 bytes) + sub $0x1000, %r11 // load the config location + + // Move 'self' to the third parameter, overwriting IMP + movq %rsi, %rdx + + // Load the block reference from the config page, and move to the second parameter + movq (%r11), %rsi + + // Jump to the block fptr + jmp *0x10(%rsi) + .align 4 // align the trampolines at 16 bytes (required for config page lookup and sizing) +EOF +} + +# Generate a single trampoline +trampoline () { +asm << 'EOF' + // Call into the dispatcher, placing our return address on the stack. + call _block_tramp_dispatch # 5 bytes + .align 4 // align the trampolines at 16 bytes (required for config page lookup and sizing) +EOF +} \ No newline at end of file diff --git a/rakelib/builder.rake b/rakelib/builder.rake index 69d2eeade..f3661d786 100644 --- a/rakelib/builder.rake +++ b/rakelib/builder.rake @@ -7,7 +7,58 @@ task :mark_gc do end end -task :files => [:config_h, :dtrace_h, :revision_h, :mark_gc] do +desc "Build the plblockimp library for imp_implementationWithBlock() support." +task :plblockimp do + # Prepare assembly trampolines for plblockimp + plblockimp_sources = [] + plblockimp_targets = [] + ARCHS.each do |a| + tramp_sources = ["", "_stret"].map { |s| + "plblockimp/#{a}/blockimp_#{a}#{s}.tramp" + } + tramp_sources.each do |s| + unless sh "plblockimp/gentramp.sh #{s} #{a} plblockimp/" + $stderr.puts "Failed to generate trampolines for plblockimp" + exit 1 + end + end + plblockimp_sources.concat( ["", "_stret"].map { |s| + "plblockimp/blockimp_#{a}#{s}_config.c" + } ) + as_sources = ["", "_stret"].map { |s| + "plblockimp/blockimp_#{a}#{s}.s" + } + as_sources.each do |s| + t = s.sub(%r{\.s$}, ".o") + unless sh "as -arch #{a} -o #{t} #{s}" + $stderr.puts "Failed to assemble trampolines for plblockimp" + exit 1 + end + plblockimp_targets << t + end + end + + # Build plblockimp as an object file for later linking + plblockimp_sources.concat( ["blockimp.c", "trampoline_table.c"].map { |s| + "plblockimp/#{s}" + } ) + cflags = $builder.cflags.scan(%r{-[^D][^\s]*}).join(' ').sub(%r{-arch},'') + plblockimp_sources.each do |s| + t = s.sub(%r{.c$}, ".o") + a = ARCHS.map { |x| "-arch #{x}" }.join(' ') + unless sh "#{CC} #{a} -c #{cflags} -DPL_BLOCKIMP_PRIVATE -o #{t} #{s}" + exit 1 + end + plblockimp_targets << t + end + plbi_o = plblockimp_targets.join(' ') + unless sh "ld #{plbi_o} -r -o #{$builder.objsdir}/plblockimp.o" + $stderr.puts "Failed to link plblockimp components" + exit 1 + end +end + +task :files => [:config_h, :dtrace_h, :revision_h, :mark_gc, :plblockimp] do end def build_objects @@ -58,6 +109,9 @@ def build_objects mv "#{output}.old", output end end + + + dispatcher_o = File.join($builder.objsdir, 'dispatcher.o') t = File.exist?(dispatcher_o) ? File.mtime(dispatcher_o) : nil vm_o = File.join($builder.objsdir, 'vm.o') @@ -75,7 +129,7 @@ desc "Create miniruby" task :miniruby => :files do $builder.config = FULL_CONFIG build_objects - $builder.link_executable('miniruby', OBJS) + $builder.link_executable('miniruby', OBJS + ['plblockimp']) end desc "Create config file" @@ -90,7 +144,7 @@ namespace :macruby do $builder.config = FULL_CONFIG build_objects dylib = "lib#{RUBY_SO_NAME}.#{NEW_RUBY_VERSION}.dylib" - $builder.link_dylib(dylib, $builder.objs - ['main', 'gc-stub']) + $builder.link_dylib(dylib, $builder.objs - ['main', 'gc-stub'] + ['plblockimp']) major, minor, teeny = NEW_RUBY_VERSION.scan(/\d+/) ["lib#{RUBY_SO_NAME}.#{major}.#{minor}.dylib", "lib#{RUBY_SO_NAME}.dylib"].each do |dylib_alias| if !File.exist?(dylib_alias) or File.readlink(dylib_alias) != dylib @@ -105,7 +159,7 @@ namespace :macruby do if ENABLE_STATIC_LIBRARY $builder.config = STATIC_CONFIG build_objects - $builder.link_archive("lib#{RUBY_SO_NAME}-static.a", $builder.objs - ['main', 'gc-stub']) + $builder.link_archive("lib#{RUBY_SO_NAME}-static.a", $builder.objs - ['main', 'gc-stub'] + ['plblockimp']) end end diff --git a/rakelib/builder/options.rb b/rakelib/builder/options.rb index a59608a8b..182432aea 100644 --- a/rakelib/builder/options.rb +++ b/rakelib/builder/options.rb @@ -180,8 +180,8 @@ def initialize(opt) end @cxxflags << " -fno-rtti" unless @cxxflags.index("-fno-rtti") @dldflags = "-dynamiclib -undefined suppress -flat_namespace -install_name #{INSTALL_NAME} -current_version #{MACRUBY_VERSION} -compatibility_version #{MACRUBY_VERSION} -exported_symbols_list #{EXPORTED_SYMBOLS_LIST}" - @cflags << ' -I./icu-1060' - @cxxflags << ' -I./icu-1060' + @cflags << ' -I./icu-1060 -I./plblockimp' + @cxxflags << ' -I./icu-1060 -I./plblockimp' if sdk sdk_flags = "--sysroot=#{sdk}" @cflags << " #{sdk_flags}" diff --git a/struct.c b/struct.c index a70a0e075..9c75b9a4a 100644 --- a/struct.c +++ b/struct.c @@ -182,9 +182,9 @@ make_struct(VALUE name, VALUE members, VALUE klass) return val; }; rb_objc_define_method(nstr, rb_id2name(id), - imp_implementationWithBlock(Block_copy(struct_ref)), 0); + pl_imp_implementationWithBlock(Block_copy(struct_ref)), 0); rb_objc_define_method(nstr, rb_id2name(rb_id_attrset(id)), - imp_implementationWithBlock(Block_copy(struct_set)), 1); + pl_imp_implementationWithBlock(Block_copy(struct_set)), 1); } }