feat(asm): Phase B.0 — validate asm shape in the compile path
Restructures the .asm_expr lowering arm into lowerAsmExpr, which validates the
asm shape with specific named diagnostics BEFORE the not-yet-implemented codegen
bail, so the user sees the real problem first. Two checklist items enforced:
- template must be a compile-time-known string ("..." or #string), not a
runtime expression;
- an asm with no value outputs must be `volatile` (else its effects could be
deleted) — mirrors Zig's rule.
Valid shapes still bail with the "codegen not yet implemented" message. Result-
type derivation + the operand auto-naming rule stay deferred to Phase C, where a
real IR op makes the result type observable/testable.
Locked with 1641-platform-asm-missing-volatile (the volatile error) and
1642-platform-asm-nop-volatile (no-output + volatile accepted → codegen bail).
zig build test green (650 corpus, 445 unit).
This commit is contained in:
@@ -1933,6 +1933,7 @@ pub const Lowering = struct {
|
||||
pub const lowerNullCoalesce = lower_expr.lowerNullCoalesce;
|
||||
pub const resolveOptionalInner = lower_expr.resolveOptionalInner;
|
||||
pub const lowerExpr = lower_expr.lowerExpr;
|
||||
pub const lowerAsmExpr = lower_expr.lowerAsmExpr;
|
||||
pub const refCapturePointee = lower_expr.refCapturePointee;
|
||||
pub const lowerBinaryOp = lower_expr.lowerBinaryOp;
|
||||
pub const lowerTupleOp = lower_expr.lowerTupleOp;
|
||||
|
||||
@@ -2189,20 +2189,48 @@ pub fn lowerExpr(self: *Lowering, node: *const Node) Ref {
|
||||
.try_expr => |te| self.lowerTry(te.operand, node.span),
|
||||
.catch_expr => |ce| self.lowerCatch(&ce, node.span),
|
||||
.caller_location => self.lowerCallerLocation(node),
|
||||
// Inline assembly parses (Phase A.1) but has no IR op / emit yet
|
||||
// (Phases C–E). Bail LOUDLY with a named diagnostic rather than falling
|
||||
// into the generic `unknown_expr` arm — the placeholder Ref makes
|
||||
// `hasErrors()` abort the build on this message (CLAUDE.md no-silent-arm).
|
||||
.asm_expr => blk: {
|
||||
if (self.diagnostics) |diags| {
|
||||
diags.addFmt(.err, node.span, "inline assembly codegen is not yet implemented (ASM stream: lowering + emit land in Phases C–E)", .{});
|
||||
}
|
||||
break :blk self.emitPlaceholder("inline_asm");
|
||||
},
|
||||
.asm_expr => |ae| self.lowerAsmExpr(&ae, node.span),
|
||||
else => self.emitError("unknown_expr", node.span),
|
||||
};
|
||||
}
|
||||
|
||||
/// Inline assembly lowering. Phase B (partial): validate the asm shape in the
|
||||
/// compile path with specific named diagnostics, THEN bail on the not-yet-
|
||||
/// implemented codegen so the user sees the real problem first (the IR op +
|
||||
/// LLVM emit land in Phases C–E; result-type derivation + the auto-naming rule
|
||||
/// move to the expression typer once lowering produces a real value). Always
|
||||
/// returns a placeholder Ref so `hasErrors()` aborts the build on whichever
|
||||
/// diagnostic fired (CLAUDE.md no-silent-arm).
|
||||
pub fn lowerAsmExpr(self: *Lowering, ae: *const ast.AsmExpr, span: ast.Span) Ref {
|
||||
const diags = self.diagnostics orelse return self.emitPlaceholder("inline_asm");
|
||||
|
||||
// (1) The template must be a compile-time-known string (a `"..."` literal or
|
||||
// a `#string` heredoc), not a runtime expression.
|
||||
const template_is_string = switch (ae.template.data) {
|
||||
.string_literal => true,
|
||||
else => false,
|
||||
};
|
||||
if (!template_is_string) {
|
||||
diags.addFmt(.err, ae.template.span, "asm template must be a compile-time-known string", .{});
|
||||
return self.emitPlaceholder("inline_asm");
|
||||
}
|
||||
|
||||
// (2) An asm with no value outputs yields no result, so it must be
|
||||
// `volatile` — otherwise its effects could be deleted. Mirrors Zig's rule.
|
||||
var n_outputs: usize = 0;
|
||||
for (ae.operands) |op| {
|
||||
if (op.role == .out_value) n_outputs += 1;
|
||||
}
|
||||
if (n_outputs == 0 and !ae.is_volatile) {
|
||||
diags.addFmt(.err, span, "asm expression with no outputs must be marked `volatile`", .{});
|
||||
return self.emitPlaceholder("inline_asm");
|
||||
}
|
||||
|
||||
// Shape is valid — codegen just isn't implemented yet (Phases C–E).
|
||||
diags.addFmt(.err, span, "inline assembly codegen is not yet implemented (ASM stream: lowering + emit land in Phases C–E)", .{});
|
||||
return self.emitPlaceholder("inline_asm");
|
||||
}
|
||||
|
||||
/// If `node` names a `for xs: (*x)` by-ref capture (an `*elem`), returns
|
||||
/// the element (pointee) type so a value-position use can auto-deref it.
|
||||
pub fn refCapturePointee(self: *Lowering, node: *const Node) ?TypeId {
|
||||
|
||||
Reference in New Issue
Block a user