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

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()

@@ -768,8 +768,9 @@ namespace {
OS << " final";
if (VD->isObjC())
OS << " @objc";
if (VD->isDynamic())
if (VD->isDynamic()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this edit necessary?

@@ -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");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These lines are too long

@slavapestov
Copy link
Member

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
Copy link
Member Author

$ 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
Copy link
Member Author

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
Copy link
Member Author

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
Copy link
Member Author

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

Dynamic functions will allow replacement of their implementation at
runtime.
'dynamic' functions are marked as [dynamically_replaceable].
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();
}
  %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.
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
Copy link
Member Author

@swift-ci Please test

1 similar comment
@aschwaighofer
Copy link
Member Author

@swift-ci Please test

@swift-ci
Copy link
Collaborator

swift-ci commented Nov 6, 2018

Build failed
Swift Test OS X Platform
Git Sha - f1b53eb

@aschwaighofer
Copy link
Member Author

@swift-ci Please test

@swift-ci
Copy link
Collaborator

swift-ci commented Nov 6, 2018

Build failed
Swift Test OS X Platform
Git Sha - f1b53eb

@swift-ci
Copy link
Collaborator

swift-ci commented Nov 6, 2018

Build failed
Swift Test Linux Platform
Git Sha - f1b53eb

@aschwaighofer
Copy link
Member Author

@swift-ci Please test

1 similar comment
@aschwaighofer
Copy link
Member Author

@swift-ci Please test

@swift-ci
Copy link
Collaborator

swift-ci commented Nov 7, 2018

Build failed
Swift Test OS X Platform
Git Sha - c77da02

@aschwaighofer
Copy link
Member Author

@swift-ci Please clean test

@swift-ci
Copy link
Collaborator

swift-ci commented Nov 7, 2018

Build failed
Swift Test OS X Platform
Git Sha - cf2eb21

@swift-ci
Copy link
Collaborator

swift-ci commented Nov 7, 2018

Build failed
Swift Test Linux Platform
Git Sha - c77da02

@aschwaighofer
Copy link
Member Author

@swift-ci Please test

@swift-ci
Copy link
Collaborator

swift-ci commented Nov 7, 2018

Build failed
Swift Test Linux Platform
Git Sha - cf2eb21

@swift-ci
Copy link
Collaborator

swift-ci commented Nov 7, 2018

Build failed
Swift Test OS X Platform
Git Sha - cf2eb21

@aschwaighofer
Copy link
Member Author

@swift-ci Please test os x platform

@aschwaighofer
Copy link
Member Author

@swift-ci Please test source compatibility

@aschwaighofer
Copy link
Member Author

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

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

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.

@Changzw
Copy link

Changzw commented Jun 3, 2020

can I use it to class method?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants