D - более дружественный asm синтаксический сахар для GDC (Dlang)

У меня была идея упростить процесс создания D + ASM-кода с использованием расширенного синтаксиса ASM GDC. Я хотел бы избавиться от необходимости вставлять \n\t маркеры повсеместно, скажем, имея отдельные строки и заставляя компилятор D объединять их. Но я открыт для других предложений. Моя попытка не удалась, потому что объединение D-строк, к сожалению, не работает в GDC во время компиляции, и мне нужен CTFE. Как и следовало ожидать, это требование, что этот кусок сахара имеет нулевую стоимость.

Я подозреваю, что мне нужно что-то сделать с миксином. Любые советы о том, куда пойти и как остаться в CTFE?

1 ответ

В GDC ошибка в том, что AssemblerTemplate в расширенном встроенном ASM должен быть строкой генерации во время компиляции, но на самом деле это не так. Что вы можете сделать, это сгенерировать строку, поместить все элементы ASM и смешать ее. Я использовал нечто подобное для пользовательской реализации системного вызова (которая все еще встроена).

module test;

template joinASMStatements(T...) {
    static if (T.length == 0) enum joinASMStatements = "";
    else static if (T.length == 1) enum joinASMStatements = T[0];
    else enum joinASMStatements = joinASMStatements!(T[0..$/2]) ~ "\\n\\t" ~  joinASMStatements!(T[$/2..$]);
}

void main() {
    ulong dst, src = 20;
    enum statements = joinASMStatements!("mov %[dst], %[src]");
    mixin("asm {" ~ statements ~ " : [dst] \"rm,r\" dst : [src] \"ri,rmi\" src }");
}

Но, честно говоря, это выглядит ужасно. Было бы проще и эстетичнее создать шаблон для всего этого для вас, который принимает массив строк. В шаблон можно добавить дополнительные элементы для обработки определенных кодов операций и автоматического добавления ограничений на их основе. Это позволило бы коду работать и на DMD, и на LDC, если бы вы этого хотели. И вы можете использовать немного магии времени компиляции, чтобы решить все это. (РЕДАКТИРОВАТЬ) Это на самом деле работает.

module test2;

import std.traits: AliasSeq;

// Input operand type
struct IOp(string _name) {
    string constraints; // A set of constraints. This is the whole thing.
    string asmName; // A label to be given to the operand (the "[<name>]" thing)
    enum name = _name; // Inner usage, to ease accessing `_name`.
}

// Output operand type
struct OOp(string _name) {
    // For variable details, see IOp comments.
    string constraints;
    string asmName;
    enum name = _name;
}

// type for register (and "cc" and "memory") clobbers
struct Clobber(string _reg) {enum reg = _reg;}

// type for goto labels
struct Goto(string _goto) {enum name = _goto;} // note that `goto` is a D keyword.

// filters out types as S!(string blah)
template filterOp(alias S, T...) {
    static if (T.length == 0) alias filterOp = AliasSeq!();
    else static if (T.length == 1) {
        static if (is(typeof(T[0]) : S!(N), string N))
            alias filterOp = AliasSeq!(T[0]);
        else
            alias filterOp = AliasSeq!();
    } else
        alias filterOp = AliasSeq!(filterOp!(S, T[0..$/2]), filterOp!(S, T[$/2..$]));
}

// joiner function for input and output operands.
template joinOps(T...) {
    static if (T.length == 0) enum joinOps = "";
    else static if (T.length == 1) enum joinOps = ((T[0].asmName != "")?"[" ~ T[0].asmName ~ "] ":"") ~ "\"" ~ T[0].constraints ~ "\" " ~ T[0].name; // The .name unescapes the name
    else enum joinOps = joinOps!(T[0..$/2]) ~ ", " ~ joinOps!(T[$/2..$]);
}

// joiner function for clobbers
template joinClobbers(T...) {
    static if (T.length == 0) enum joinClobbers = "";
    else static if (T.length == 1) enum joinClobbers = "\"" ~ T[0].reg ~ "\"";
    else enum joinClobbers = joinClobbers!(T[0..$/2]) ~ ", " ~ joinClobbers!(T[$/2..$]);
}

// joiner function for goto labels
template joinGotos(T...) {
    static if (T.length == 0) enum joinGotos = "";
    else static if (T.length == 1) enum joinGotos = T[0].name; // Here the label is unescaped
    else enum joinGotos = joinGotos!(T[0..$/2]) ~ ", " ~ joinGotos!(T[$/2..$]); // Recursively executes itself on halves of the input. Eventually, the halves become lengths of `1` or `0`, and they are actually evaluated.
}

// joiner function for instructions.
template joinInstrs(string[] instrs) {
    static if (instrs.length == 0) enum joinInstrs = "";
    else static if (instrs.length == 1) enum joinInstrs = instrs[0];
    else enum joinInstrs = joinInstrs!(instrs[0..$/2]) ~ "\\n\\t" ~ joinInstrs!(instrs[$/2..$]);
}

// complete assembly generator function. Output is to be mixed in.
template ASM(string[] ops, T...) {
    enum iops = joinOps!(filterOp!(IOp, T));
    enum oops = joinOps!(filterOp!(OOp, T));
    enum clobbers = joinClobbers!(filterOp!(Clobber, T));
    enum gotos = joinGotos!(filterOp!(Goto, T));
    enum instrs = "\"" ~ joinInstrs!(ops) ~ "\"";
    enum ASM = "asm { " ~ instrs ~ " : " ~ oops ~ " : " ~ iops ~ " : " ~ clobbers ~ " : " ~ gotos ~ "; }";
}

void main() {
    ulong src = 24, dst;
    mixin(ASM!(["mov %[dst], %[src]"], IOp!"src"("ri,rmi", "src"), OOp!"dst"("=rm,r", "dst")));
}

ЗАМЕТКИ:

  • Вместо добавления = для вывода ограничений, вы можете объединить IOp а также OOp и дифференцировать ввод и вывод на основе ограничений (см. документы GCC для выходных ограничений, которые работают). Все остальное будет входными операндами под общим struct Op или похожие.
    • Это моя первая попытка, и есть лучшие способы сделать это и упростить код, который я пропустил.

Кстати, спасибо, что заставили меня думать об этом! Мне нужно реализовать это для моих собственных вещей сейчас.

Другие вопросы по тегам