Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic function replacement #20333

Merged

Conversation

@aschwaighofer
Copy link
Member

commented Nov 5, 2018

Implement dynamic function replacement as described in https://forums.swift.org/t/dynamic-method-replacement.

Allow dynamic on non-@objc classes, struct, and enum functions, properties, initializers.

Dynamic replacements are currently expressed by adding the @_dynamicReplacement(for:) attribute on functions defined in an extensions of the type or at the top-level.

// Module A
struct Foo {
 dynamic func bar() {}
}
// Module B
extension Foo {
  @_dynamicReplacement(for: bar()0
  func barReplacement() {
    ...
    // Calls previously active implementation of bar()
    bar()
  }

Dynamic replacements become active at load time of the module containing the replacement.

The runtime implementation allows for an implementation in the future where dynamic replacements can be grouped in an 'dynamic replacement' scope and the scope can be dynamically enabled/disabled.

Dynamic replacements chain such that a dynamic replacement can call the previously active implementation of the dynamically replaced function.

dynamic_replacement_scope AGroupOfReplacements {
   extension Foo {
     func replacedFunc() {}
   }
   extension AnotherType {
     func replacedFunc() {}
   }
}

AGroupOfReplacements.enable()
...
AGroupOfReplacements.disable()
lib/AST/ASTDumper.cpp Outdated
@@ -768,8 +768,9 @@ namespace {
OS << " final";
if (VD->isObjC())
OS << " @objc";
if (VD->isDynamic())
if (VD->isDynamic()) {

This comment has been minimized.

Copy link
@slavapestov

slavapestov Nov 5, 2018

Member

Is this edit necessary?

lib/SIL/SILVerifier.cpp Outdated
@@ -1402,6 +1402,14 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {

SILFunction *RefF = FRI->getReferencedFunction();

if (isa<FunctionRefInst>(FRI))
require(!RefF->isDynamicallyReplaceable(), "function_ref cannot reference a [dynamically_replaceable] function");

This comment has been minimized.

Copy link
@slavapestov

slavapestov Nov 5, 2018

Member

These lines are too long

@slavapestov

This comment has been minimized.

Copy link
Member

commented Nov 5, 2018

How does it work if a class method is dynamically replaceable? Is it still called via class_method and then we have to emit a vtable thunk which does the dynamic_function_ref?

@aschwaighofer

This comment has been minimized.

Copy link
Member Author

commented Nov 5, 2018

$ cat C.swift
class Foo {
  dynamic func bar() {}
}
// Foo.bar()
sil hidden [dynamically_replacable] @$s1C3FooC3baryyF : $@convention(method) (@guaranteed Foo) -> () {
// %0                                             // user: %1
bb0(%0 : @guaranteed $Foo):
  debug_value %0 : $Foo, let, name "self", argno 1 // id: %1
  %2 = tuple ()                                   // user: %3
  return %2 : $()                                 // id: %3
}

sil_vtable Foo {
  #Foo.bar!1: (Foo) -> () -> () : @$s1C3FooC3baryyF     // Foo.bar()
...
@aschwaighofer

This comment has been minimized.

Copy link
Member Author

commented Nov 5, 2018

The [dynamically_replaceable] SIL function does the dispatch:

@"$s1C3FooC3baryyFTX" = hidden global %swift.dyn_repl_link_entry { i8* bitcast (void (%T1C3FooC*)* @"$s1C3FooC3baryyFTI" to i8*), %swift.dyn_repl_link_entry* null }, align 8

define hidden swiftcc void @"$s1C3FooC3baryyF"(%T1C3FooC* swiftself) #0 {
entry:
  %1 = load i8*, i8** getelementptr inbounds (%swift.dyn_repl_link_entry, %swift.dyn_repl_link_entry* @"$s1C3FooC3baryyFTX", i32 0, i32 0), align 8
  %2 = bitcast i8* %1 to void (%T1C3FooC*)*
  call swiftcc void %2(%T1C3FooC* swiftself %0)
  ret void
}


define hidden swiftcc void @"$s1C3FooC3baryyFTI"(%T1C3FooC* swiftself) #1 {
entry:
  %self.debug = alloca %T1C3FooC*, align 8
  %1 = bitcast %T1C3FooC** %self.debug to i8*
  call void @llvm.memset.p0i8.i64(i8* align 8 %1, i8 0, i64 8, i1 false)
  store %T1C3FooC* %0, %T1C3FooC** %self.debug, align 8
  ret void
}
@aschwaighofer

This comment has been minimized.

Copy link
Member Author

commented Nov 5, 2018

dynamic_function_ref is different to function_ref only in that it is verified that we call a SIL function that is [dynamically_replaceable].

@aschwaighofer

This comment has been minimized.

Copy link
Member Author

commented Nov 5, 2018

To answer your question: yes we still dispatch via class_method to the callee.

aschwaighofer added some commits Aug 21, 2018

Allow dynamic without @objc in -swift-version 5
Dynamic functions will allow replacement of their implementation at
runtime.
Add [dynamically_replacable] to SILFunctions
'dynamic' functions are marked as [dynamically_replaceable].
IRGen: Add implementation for dynamically replaceable functions
A dynamically replaceable function calls through a global variable that
holds the function pointer.

struct ChainEntry {
   void *(funPtr)();
   struct ChainEntry *next;
}

ChainEntry dynamicallyReplaceableVar;

void dynamicallyReplaceableFunction() {
  dynamicallyReplaceableVar.funPtr()
}

dynamic replacements will be chainable so the global variable also
functions as the root entry in the chain of replacements.

A dynamic replacement functions can call the previous implementation by
going through its chain entry.

ChainEntry chainEntryOf_dynamic_replacement_for_foo;

void dynamic_replacement_for_foo() {
   // call the previous (original) implementation.
   chainEntryOf_dynamic_replacement_for_foo.funPtr();
}
Add new SIL instruction for calling dynamically_replaceable funtions
  %0 = dynamic_function_ref @dynamically_replaceable_function
  apply %0()
  Calls a [dynamically_replaceable] function.

  %0 = prev_dynamic_function_ref @dynamic_replacement_function
  apply %0
  Calls the previous implementation that dynamic_replacement_function
  replaced.

@aschwaighofer aschwaighofer force-pushed the aschwaighofer:dynamic_function_replacement branch Nov 6, 2018

aschwaighofer added some commits Nov 5, 2018

Parser/Sema/SILGen changes for @_dynamicReplacement(for:)
Dynamic replacements are currently written in extensions as

extension ExtendedType {
  @_dynamicReplacement(for: replacedFun())
  func replacement() { }
}

The runtime implementation allows an implementation in the future where
dynamic replacements are gather in a scope and can be dynamically
enabled and disabled.

For example:

dynamic_extension_scope CollectionOfReplacements {
  extension ExtentedType {
    func replacedFun() {}
  }

  extension ExtentedType2 {
    func replacedFun() {}
  }
}

CollectionOfReplacements.enable()
CollectionOfReplacements.disable()

@aschwaighofer aschwaighofer force-pushed the aschwaighofer:dynamic_function_replacement branch to f1b53eb Nov 6, 2018

@aschwaighofer

This comment has been minimized.

Copy link
Member Author

commented Nov 6, 2018

@swift-ci Please test

1 similar comment
@aschwaighofer

This comment has been minimized.

Copy link
Member Author

commented Nov 6, 2018

@swift-ci Please test

@swift-ci

This comment has been minimized.

Copy link
Contributor

commented Nov 6, 2018

Build failed
Swift Test OS X Platform
Git Sha - f1b53eb

@aschwaighofer

This comment has been minimized.

Copy link
Member Author

commented Nov 6, 2018

@swift-ci Please test

@swift-ci

This comment has been minimized.

Copy link
Contributor

commented Nov 6, 2018

Build failed
Swift Test OS X Platform
Git Sha - f1b53eb

@swift-ci

This comment has been minimized.

Copy link
Contributor

commented Nov 6, 2018

Build failed
Swift Test Linux Platform
Git Sha - f1b53eb

@aschwaighofer

This comment has been minimized.

Copy link
Member Author

commented Nov 6, 2018

@swift-ci Please test

1 similar comment
@aschwaighofer

This comment has been minimized.

Copy link
Member Author

commented Nov 6, 2018

@swift-ci Please test

@swift-ci

This comment has been minimized.

Copy link
Contributor

commented Nov 7, 2018

Build failed
Swift Test OS X Platform
Git Sha - c77da02

@aschwaighofer

This comment has been minimized.

Copy link
Member Author

commented Nov 7, 2018

@swift-ci Please clean test

@swift-ci

This comment has been minimized.

Copy link
Contributor

commented Nov 7, 2018

Build failed
Swift Test OS X Platform
Git Sha - cf2eb21

@swift-ci

This comment has been minimized.

Copy link
Contributor

commented Nov 7, 2018

Build failed
Swift Test Linux Platform
Git Sha - c77da02

@aschwaighofer

This comment has been minimized.

Copy link
Member Author

commented Nov 7, 2018

@swift-ci Please test

@swift-ci

This comment has been minimized.

Copy link
Contributor

commented Nov 7, 2018

Build failed
Swift Test Linux Platform
Git Sha - cf2eb21

@swift-ci

This comment has been minimized.

Copy link
Contributor

commented Nov 7, 2018

Build failed
Swift Test OS X Platform
Git Sha - cf2eb21

@aschwaighofer

This comment has been minimized.

Copy link
Member Author

commented Nov 7, 2018

@swift-ci Please test os x platform

@aschwaighofer

This comment has been minimized.

Copy link
Member Author

commented Nov 7, 2018

@swift-ci Please test source compatibility

@aschwaighofer

This comment has been minimized.

Copy link
Member Author

commented Nov 7, 2018

The source compatibility failure is the same that also occurs on trunk.

@aschwaighofer aschwaighofer merged commit 44b3a47 into apple:master Nov 7, 2018

4 of 6 checks passed

Swift Source Compatibility Suite on macOS Platform (Debug)
Details
Swift Source Compatibility Suite on macOS Platform (Release)
Details
Swift Test Linux Platform 11454 tests run, 10335 skipped, 0 failed.
Details
Swift Test Linux Platform (smoke test)
Details
Swift Test OS X Platform 57460 tests run, 2430 skipped, 0 failed.
Details
Swift Test OS X Platform (smoke test)
Details
@Whirlwind

This comment has been minimized.

Copy link

commented Jul 26, 2019

Hi, I could not work on the scope:

dynamic_replacement_scope AGroupOfReplacements {
   extension Foo {
     func replacedFunc() {}
   }
   extension AnotherType {
     func replacedFunc() {}
   }
}

AGroupOfReplacements.enable()
...
AGroupOfReplacements.disable()

Is it public?

By the way, if there are two replacement methods to replace the original method, how to call the chain?

public class Core {
    public dynamic func originalFunc() { print("core") }
}

extension Core {
    @_dynamicReplacement(for: originalFunc())
    public func a() { 
         print("a")
         originalFunc()
    }
}

extension Core {
    @_dynamicReplacement(for: originalFunc())
    public func b() { 
         print("b")
         originalFunc()
    }
}

I hope to get the

a
b
core

Or, get the

b
a
core

But now, I get the

b
core

Lost the a.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants
You can’t perform that action at this time.