Jump to page: 1 2 3
Thread overview
March 08

What if instead of importing C files like D modules, we could write bits of C code directly in the middle of our D code, like we do with inline ASM?

It might look something like this:

void main()
{
    mixin(C) {
        #include <stdio.h>
        printf("Hello from C!\n");
    }
}

Here's how it could work:

  1. The compiler takes the content of the mixin(C) block and passes it through the external C preprocessor.
  2. The result of (1) is parsed as a C AST fragment using the ImportC parser.
  3. The result of (2) is spliced into the AST in place of the mixin(C) block, and undergoes semantic analysis using ImportC semantics.

Mixin C would solve two big issues with the current ImportC approach: the poor preprocessor support, and the conflicts between .c and .d files in the compiler's import paths.

Because Mixin C runs the preprocessor at the point of usage rather than the point of definition, it allows you to make full use of C APIs that rely on the preprocessor, without having to translate macros to D (either automatically or by hand).

Because Mixin C blocks appear as code fragments inside .d files, rather than as separate .c files, you'll never have to worry about accidentally importing a C file when you meant to import a D module.

By the way, if you did want to treat a .c or .h file like its own module, you'd still be able to do so with Mixin C. Just write a simple .d wrapper, like this:

module libwhatever;

mixin(C) {
    #include <libwhatever.h>
}

I haven't spent much time fleshing out the details of this idea, but it seems pretty promising. What do you guys think?

March 08
ImportC is fundamentally a compiler specific extension.

Having raw C blocks in a D file gives me concern for IDE's wrt. syntax highlighting.

We do have a comparable feature with inline assembly support in ldc/gdc where it uses strings.

I would suggest that this is the direction to go in rather than a raw code block.

It's a good idea that I do think is the right approach to the problem.
March 08
On Friday, 8 March 2024 at 03:37:02 UTC, Richard (Rikki) Andrew Cattermole wrote:
> Having raw C blocks in a D file gives me concern for IDE's wrt. syntax highlighting.
>
> We do have a comparable feature with inline assembly support in ldc/gdc where it uses strings.
>
> I would suggest that this is the direction to go in rather than a raw code block.

Yeah that's reasonable. Honestly it would barely even look different if you use a q{...} string:

    mixin(C) q{
        #include <stdio.h>
        printf("Hello from C!\n");
    };

I guess the lexer currently barfs on #include, but surely we can bend the rules on that if we need to.
March 08
On 08/03/2024 5:02 PM, Paul Backus wrote:
> On Friday, 8 March 2024 at 03:37:02 UTC, Richard (Rikki) Andrew Cattermole wrote:
>> Having raw C blocks in a D file gives me concern for IDE's wrt. syntax highlighting.
>>
>> We do have a comparable feature with inline assembly support in ldc/gdc where it uses strings.
>>
>> I would suggest that this is the direction to go in rather than a raw code block.
> 
> Yeah that's reasonable. Honestly it would barely even look different if you use a q{...} string:
> 
>      mixin(C) q{
>          #include <stdio.h>
>          printf("Hello from C!\n");
>      };
> 
> I guess the lexer currently barfs on #include, but surely we can bend the rules on that if we need to.

I was thinking about a new kind of string, one that produces a struct that has both a language string that the user provided as well as the contents.

That way editors can syntax highlight if they understand it, or ignore it if they don't.

Might be overkill, but it does have some interesting possibilities.
March 08

On Friday, 8 March 2024 at 03:23:12 UTC, Paul Backus wrote:

>

What if instead of importing C files like D modules, we could write bits of C code directly in the middle of our D code, like we do with inline ASM?

Why not create a separate file name extension, such as 'dc',then like this:

//aa.dc
#include <stdio.h>
printf("Hello from C!\n");

//b.d
import aa;
March 08

On Friday, 8 March 2024 at 03:23:12 UTC, Paul Backus wrote:

>
module libwhatever;

mixin(C) {
    #include <libwhatever.h>
}

I haven't spent much time fleshing out the details of this idea, but it seems pretty promising. What do you guys think?

The first thing that comes to mind is that it'll allow doing things inline that would otherwise be impossible in D.

import std.stdio;

extern(C) int func(int x);

mixin(C) {
    // gdc supports these asm-declarations via gcc (ldc should too via clang)
    asm("
.globl func
    .type func, @function
    func:
    .cfi_startproc
    movl %edi, %eax
    addl $1, %eax
    ret
    .cfi_endproc
");
}

int main()
{
    int n = func(72);
    // mixin(C) not necessary as gdc+ldc support this natively with asm{""::;}
    mixin(C) {
        asm ("leal (%0,%0,4),%0"
             : "=r" (n)
             : "0" (n));
    }
    writeln("73*5 = ", n);  // 73*5 = 365

    // ditto, asm{""}, but this is purely for presentation.
    mixin(C) {
        asm ("movq $60, %rax\n"
             "movq $2,  %rdi\n"
             "syscall");
    }
    assert(0);
}
March 08

On Friday, 8 March 2024 at 03:23:12 UTC, Paul Backus wrote:

>

I haven't spent much time fleshing out the details of this idea, but it seems pretty promising. What do you guys think?

Translation of C macros to D can sometimes give incorrect behavior. With the current implementation, it does that silently, and that obviously raises questions about the reliability of the final product. My proposal is to allow the user to compile C code inside unit tests so there's at least a chance of catching bugs. What you are proposing would make that possible.

March 08

On Friday, 8 March 2024 at 14:39:46 UTC, Lance Bachmeier wrote:

>

On Friday, 8 March 2024 at 03:23:12 UTC, Paul Backus wrote:

>

I haven't spent much time fleshing out the details of this idea, but it seems pretty promising. What do you guys think?

Translation of C macros to D can sometimes give incorrect behavior. With the current implementation, it does that silently, and that obviously raises questions about the reliability of the final product. My proposal is to allow the user to compile C code inside unit tests so there's at least a chance of catching bugs. What you are proposing would make that possible.

And I realize you have addressed the macro thing too, but I think this is separately a valid use case.

March 08

On Friday, 8 March 2024 at 03:23:12 UTC, Paul Backus wrote:

>

What if instead of importing C files like D modules, we could write bits of C code directly in the middle of our D code, like we do with inline ASM?

Are multiple mixin(C) blocks evaluated in the same context? Symbols and macros from one mixin(C) block could then be used in another mixin(C) block, like in this example:

mixin(C) {
#include <stdio.h>
};
void main()
{
    mixin(C) {
        printf("test\n");
    }
}

The preprocessor call for the second block would need to know all macros from the first call.

Can code in mixin(C) statements access local variables from D? How would name conflicts be resolved when an identifier exists both in the current module and a C header file? In the following example BUFSIZ is both a local variable and a macro from a C header:

void main()
{
    int BUFSIZ = 5;
    mixin(C) {
        #include <stdio.h>
        printf("BUFSIZ = %d\n", BUFSIZ);
    }
}

Are variables declared in mixin(C) statements interpreted as global or local variables?

void main()
{
    mixin(C) {
        #include <stdio.h>
        fprintf(stderr, "test\n");
    }
}

The header declares variable stderr. If this is now a local variable, because the header is included inside a function, it could cause problems. Maybe this could be solved by treating C variables marked with extern as globals.

March 08

On Friday, 8 March 2024 at 17:06:21 UTC, Tim wrote:

>

Are multiple mixin(C) blocks evaluated in the same context? Symbols and macros from one mixin(C) block could then be used in another mixin(C) block, like in this example:

mixin(C) {
#include <stdio.h>
};
void main()
{
    mixin(C) {
        printf("test\n");
    }
}

The preprocessor call for the second block would need to know all macros from the first call.

Each block is preprocessed separately, and the result of preprocessing is then evaluated (type-checked and compiled) in the context of the enclosing D scope. So, symbols are visible across different blocks, but preprocessor macros are not.

Your example would work, because it would expand to this:

extern(C) int printf(const(char)* format, ...);
// Other definitions from stdio.h ...

void main()
{
    printf("test\n");
}

But this example would not work:

mixin(C) {
    #include <stdio.h>
    #define info(s) printf("[info] %s\n", s);
}

void main()
{
    mixin(C) {
        info("test"); // error - undefined identifier 'info'
    }
}
>

Can code in mixin(C) statements access local variables from D? How would name conflicts be resolved when an identifier exists both in the current module and a C header file? In the following example BUFSIZ is both a local variable and a macro from a C header:

void main()
{
    int BUFSIZ = 5;
    mixin(C) {
        #include <stdio.h>
        printf("BUFSIZ = %d\n", BUFSIZ);
    }
}

Name lookup in mixin(C) blocks would follow the normal D scoping rules.

In this example, since BUFSIZ is a macro, it would be expanded by the preprocessor before the D compiler even parses the C code, and the value from stdio.h would be printed.

If BUFSIZ were a variable instead of a macro, then you would get a compile-time error for defining two variables with the same name in the same scope.

>

Are variables declared in mixin(C) statements interpreted as global or local variables?

void main()
{
    mixin(C) {
        #include <stdio.h>
        fprintf(stderr, "test\n");
    }
}

The header declares variable stderr. If this is now a local variable, because the header is included inside a function, it could cause problems. Maybe this could be solved by treating C variables marked with extern as globals.

I believe the C standard actually requires such variables to be treated as globals. The relevant sections are 6.2.2 Linkages of identifiers and 6.2.4 Storage durations of objects. So, assuming the D compiler implements the C standard correctly, this should Just Work.

« First   ‹ Prev
1 2 3