On Friday, 28 April 2017 at 10:09:32 UTC, Moritz Maxeiner wrote:
On Friday, 28 April 2017 at 09:52:29 UTC, Petar Kirov [ZombineDev] wrote:

AST introspection - given a function definition (!= declaration, i.e. the body is available) f, the expression __traits(ast, f) should return an instance of FuncDeclaration (https://github.com/dlang/dmd/blob/197ff0fd84b7b101f47458ab06e5b6f95959941e/src/ddmd/astnull.d#L151) with all of its members filled. The class-es used to represent the AST should be plain old data types (i.e. should contain no functionality). The AST as produced by the parser should be returned, without any semantic analysis (which can modify it). No backwards compatibility guarantees should be made about __traits(ast, f) at least for a couple of years.


This sounds interesting, but could you expand on what this enables/improves compared to CTFE+mixins?

CTFE and mixins are building blocks, needed to for my idea to actually useful. Currently if you want to introspect a piece of code (function body) at compile time, you need to duplicate the code in a string and then pass this string to a CTFE-able D parser. As you can imagine, even with Stefan's new CTFE engine, this would be orders of magnitude slower than just reusing the work that parser inside the compiler *has already done* anyway. This makes such code introspection
infeasible for large projects. Strings (at least until mixined)
can contain uncompilable source (though lexically valid, in the case of q{}), further complicating the matter. Additionally, the fact that you need to keep the source code and the strings in sync is just stupid, violating DRY
at a whole new level.

In addition to AST introspection, AST transformation should be as easy as:

mixin template profileFunctionStatements(alias func, string newFunctionName)
{
    enum funcAst = __traits(ast, func);
    enum newAst = insertProfiling(funcAst, newFunctionName);
    mixin(newAst.toString());

    // a further optimization would be AST mixins, which
    // could directly be used by the compiler, instead of
    // first converting the AST to string and then
    // parsing it after mixing:
    mixin(newAst);
}

void main()
{
    int local = 42;

    void fun(int[] arr)
    {
        import std.conv : text;
        import std.file : remove, write;
        arr[] += local;
        string s = text(arr);
        "delete-me.txt".write(s);
    }

    mixin profileFunctionStatements!(print, `funInstrumented`);

    import std.array : array;
    import std.range : iota;
    funInstrumented(10000.iota.array);
}

Output:
{  arr[] += local;  } took 0.002 miliseconds to execute.
{  string s = text(arr);  } took 1.8052 miliseconds to execute.
{ "delete-me.txt".write(s); } took 7.746 miliseconds to execute.

Where funInstrumented could be generated to something like this:

void funInstrumented(int[] arr)
{
import std.datetime : __Sw = StopWatch, __to = to; // generated
    import std.stdio : __wfln = writefln; // generated
    import std.conv : text;
    import std.file : remove, write;

    __Sw __sw; // generated

    __sw.start(); // generated
    arr[] += local;
    __sw.stop(); // generated
    __wfln("{ %s } took %s miliseconds to execute.",
        q{ arr[] += local; },
        __sw.peek().__to!("msecs", double)); // generated

    __sw.start(); // generated
    string s = text(arr);
    __sw.stop(); // generated
    __wfln("{ %s } took %s miliseconds to execute.",
        q{ string s = text(arr); },
        __sw.peek().__to!("msecs", double)); // generated

    __sw.start(); // generated
    "delete-me.txt".write(s);
     __sw.stop(); // generated
    __wfln("{%s} took %s miliseconds to execute.",
        q{ "delete-me.txt".write(s); },
        __sw.peek().__to!("msecs", double)); // generated
}

Other applications include:
* compiling/transpiling D functions to targets
like JS, SPIR-V, WebAssembly, etc. using CTFE.
* CTFE-driven code diagnostics (linting)
* Adding semantic to user defined attributes:
E.g. @asyncSafe attribute for use in libraries like vibe.d that allows calling only functions marked as @asyncSafe from @asyncSafe code. That way libraries can introduce *and enforce* correct use of UDAs without any need for language changes.
* ...

Reply via email to