feat(asm): Phase A.1 — parse asm { … } into AsmExpr; loud lowering bail
`asm volatile? { "tmpl", [name]? "constraint" (-> Type | = expr), …,
clobbers(.…) }` now parses into a flat-operand AsmExpr/AsmOperand (ast.zig +
parser.zig parseAsmExpr, dispatched from parsePrimary on .kw_asm). `volatile`
and `clobbers` are recognized contextually (not reserved). `-> @place`
write-through is rejected with a clear "Phase 2" parse error.
Codegen is not implemented yet (IR op + LLVM emit are Phases C–E), so lowering
bails LOUD + named via an explicit .asm_expr arm in lower/expr.zig (not the
generic unknown_expr else) — emitPlaceholder makes hasErrors() abort the build
on the message.
The new asm_expr tag forced (and got) arms in three exhaustive Node.Data
switches: sema.zig analyzeNode + findNodeAtOffset, semantic_diagnostics.zig
checkBindingNames — each recurses into template + operand payloads.
Design: adopted the operand auto-naming rule (design §II.5) — name auto-derived
from a {reg} pin, explicit [name] only when it differs or for register-class
operands, echo form rejected. Typing-stage rule; parser stores name: ?[]const u8.
Locked with examples/1640-platform-asm-parse.sx (multi-output divmod: named
operands, register pins, clobbers — parses then bails, called from main).
Also files issue 0137 (pre-existing, orthogonal: `sx run` with no `main`
segfaults via an unguarded JIT entry lookup in target.zig — not an asm bug).
zig build test green (648 corpus, 445 unit).
This commit is contained in:
37
src/ast.zig
37
src/ast.zig
@@ -95,6 +95,7 @@ pub const Node = struct {
|
||||
ffi_intrinsic_call: FfiIntrinsicCall,
|
||||
runtime_class_decl: RuntimeClassDecl,
|
||||
jni_env_block: JniEnvBlock,
|
||||
asm_expr: AsmExpr,
|
||||
|
||||
pub fn declName(self: Data) ?[]const u8 {
|
||||
return switch (self) {
|
||||
@@ -222,6 +223,42 @@ pub const StringLiteral = struct {
|
||||
is_raw: bool = false,
|
||||
};
|
||||
|
||||
/// Inline assembly expression: `asm volatile? { "tmpl", <operands…>,
|
||||
/// clobbers(.…) }` (ASM stream, design §II.3). A flat `operands` list in source
|
||||
/// order — that order keys the `%N`/`%[name]` indices and the LLVM constraint
|
||||
/// string. The result type is derived in Sema from the `out_value` operands
|
||||
/// (0→void, 1→T, N→tuple). Parsed in Phase A.1; lowering bails loudly until the
|
||||
/// IR op + emit land (Phases C–E).
|
||||
pub const AsmExpr = struct {
|
||||
/// Template: a string-literal / `#string` heredoc node (a comptime string).
|
||||
template: *Node,
|
||||
is_volatile: bool = false,
|
||||
/// Declaration order preserved (= `%N` indexing).
|
||||
operands: []const AsmOperand,
|
||||
/// Dot-names from `clobbers(.…)`: e.g. "rcx", "cc", "memory".
|
||||
clobbers: []const []const u8,
|
||||
};
|
||||
|
||||
pub const AsmOperand = struct {
|
||||
/// Optional `[name]`; null when not written. The *effective* name (for
|
||||
/// `%[name]` and the result tuple field) is computed in Sema: explicit
|
||||
/// `[name]`, else auto-derived from a `{reg}` pin in `constraint` (design
|
||||
/// §II.5 naming rule).
|
||||
name: ?[]const u8 = null,
|
||||
/// Verbatim constraint, e.g. "={rax}", "=r", "+r", "{rdi}", "r".
|
||||
constraint: []const u8,
|
||||
role: Role,
|
||||
/// `out_value` → a Type node; `input` → an expression node. (`out_place`
|
||||
/// payload is a write-through place expr — Phase 2, not parsed in A.1.)
|
||||
payload: *Node,
|
||||
|
||||
pub const Role = enum {
|
||||
out_value, // `-> Type` value output; N of these → a tuple result
|
||||
out_place, // `-> @place` write-through to storage (Phase 2)
|
||||
input, // `= expr`
|
||||
};
|
||||
};
|
||||
|
||||
pub const Identifier = struct {
|
||||
name: []const u8,
|
||||
/// True when written as a backtick raw identifier (`` `i2 ``). Carried so a
|
||||
|
||||
Reference in New Issue
Block a user