D - GDC (Dlang) 更友好的 asm 语法糖
D - friendlier asm syntactic sugar for GDC (Dlang)
我有一个想法可以简化使用 GDC 的扩展 asm 语法创建 D plus asm 代码的过程。我想摆脱在整个地方插入 \n\t 标记的需要,例如,具有单独的字符串并让 D 编译器连接它们。但我愿意接受其他建议。我的尝试失败了,因为不幸的是,连接 D 字符串在 GDC 编译时 中不起作用,我需要 CTFE。正如您所期望的那样,要求这块糖的成本为零。
我想我需要用 mixin 做点什么。关于去哪里以及如何留在 CTFE 内的任何提示?
GDC 有问题,因为扩展内联 ASM 中的 AssemblerTemplate 应该 是编译时生成的字符串,但实际上不是't。您 可以 做的是生成字符串,将所有 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
或类似的输入操作数。
- 这是我的第一次尝试,有 更好的方法可以做到这一点并简化我忽略的代码。
顺便说一句,谢谢你让我想到了这个!我现在需要为我自己的东西实现它。
我有一个想法可以简化使用 GDC 的扩展 asm 语法创建 D plus asm 代码的过程。我想摆脱在整个地方插入 \n\t 标记的需要,例如,具有单独的字符串并让 D 编译器连接它们。但我愿意接受其他建议。我的尝试失败了,因为不幸的是,连接 D 字符串在 GDC 编译时 中不起作用,我需要 CTFE。正如您所期望的那样,要求这块糖的成本为零。
我想我需要用 mixin 做点什么。关于去哪里以及如何留在 CTFE 内的任何提示?
GDC 有问题,因为扩展内联 ASM 中的 AssemblerTemplate 应该 是编译时生成的字符串,但实际上不是't。您 可以 做的是生成字符串,将所有 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
或类似的输入操作数。- 这是我的第一次尝试,有 更好的方法可以做到这一点并简化我忽略的代码。
顺便说一句,谢谢你让我想到了这个!我现在需要为我自己的东西实现它。