inline assembly: use types

until now these were stringly typed.

it's kinda obvious when you think about it.
This commit is contained in:
Andrew Kelley 2025-07-11 22:03:00 -07:00
parent 6002514b72
commit fcafc63f3d
25 changed files with 1574 additions and 251 deletions

View file

@ -1,6 +1,10 @@
//! Types and values provided by the Zig language.
const builtin = @import("builtin");
const std = @import("std.zig");
const root = @import("root");
pub const assembly = @import("builtin/assembly.zig");
/// `explicit_subsystem` is missing when the subsystem is automatically detected,
/// so Zig standard library has the subsystem detection logic here. This should generally be
@ -1100,6 +1104,3 @@ pub noinline fn returnError() void {
st.instruction_addresses[st.index] = @returnAddress();
st.index += 1;
}
const std = @import("std.zig");
const root = @import("root");

1026
lib/std/builtin/assembly.zig Normal file

File diff suppressed because it is too large Load diff

View file

@ -740,6 +740,7 @@ pub const SimpleComptimeReason = enum(u32) {
generic_call_target,
wasm_memory_index,
work_group_dim_index,
clobber,
// Evaluating at comptime because types must be comptime-known.
// Reasons other than `.type` are just more specific messages.
@ -820,6 +821,7 @@ pub const SimpleComptimeReason = enum(u32) {
.generic_call_target => "generic function being called must be comptime-known",
.wasm_memory_index => "wasm memory index must be comptime-known",
.work_group_dim_index => "work group dimension index must be comptime-known",
.clobber => "clobber must be comptime-known",
.type => "types must be comptime-known",
.array_sentinel => "array sentinel value must be comptime-known",

View file

@ -634,6 +634,7 @@ pub fn firstToken(tree: Ast, node: Node.Index) TokenIndex {
.@"nosuspend",
.asm_simple,
.@"asm",
.asm_legacy,
.array_type,
.array_type_sentinel,
.error_value,
@ -1047,6 +1048,11 @@ pub fn lastToken(tree: Ast, node: Node.Index) TokenIndex {
n = @enumFromInt(tree.extra_data[@intFromEnum(members.end) - 1]); // last parameter
}
},
.asm_legacy => {
_, const extra_index = tree.nodeData(n).node_and_extra;
const extra = tree.extraData(extra_index, Node.AsmLegacy);
return extra.rparen + end_offset;
},
.@"asm" => {
_, const extra_index = tree.nodeData(n).node_and_extra;
const extra = tree.extraData(extra_index, Node.Asm);
@ -1885,6 +1891,19 @@ pub fn asmSimple(tree: Ast, node: Node.Index) full.Asm {
.template = template,
.items = &.{},
.rparen = rparen,
.clobbers = .none,
});
}
pub fn asmLegacy(tree: Ast, node: Node.Index) full.AsmLegacy {
const template, const extra_index = tree.nodeData(node).node_and_extra;
const extra = tree.extraData(extra_index, Node.AsmLegacy);
const items = tree.extraDataSlice(.{ .start = extra.items_start, .end = extra.items_end }, Node.Index);
return tree.legacyAsmComponents(.{
.asm_token = tree.nodeMainToken(node),
.template = template,
.items = items,
.rparen = extra.rparen,
});
}
@ -1896,6 +1915,7 @@ pub fn asmFull(tree: Ast, node: Node.Index) full.Asm {
.asm_token = tree.nodeMainToken(node),
.template = template,
.items = items,
.clobbers = extra.clobbers,
.rparen = extra.rparen,
});
}
@ -2192,8 +2212,8 @@ fn fullSwitchCaseComponents(tree: Ast, info: full.SwitchCase.Components, node: N
return result;
}
fn fullAsmComponents(tree: Ast, info: full.Asm.Components) full.Asm {
var result: full.Asm = .{
fn legacyAsmComponents(tree: Ast, info: full.AsmLegacy.Components) full.AsmLegacy {
var result: full.AsmLegacy = .{
.ast = info,
.volatile_token = null,
.inputs = &.{},
@ -2253,6 +2273,29 @@ fn fullAsmComponents(tree: Ast, info: full.Asm.Components) full.Asm {
return result;
}
fn fullAsmComponents(tree: Ast, info: full.Asm.Components) full.Asm {
var result: full.Asm = .{
.ast = info,
.volatile_token = null,
.inputs = &.{},
.outputs = &.{},
};
if (tree.tokenTag(info.asm_token + 1) == .keyword_volatile) {
result.volatile_token = info.asm_token + 1;
}
const outputs_end: usize = for (info.items, 0..) |item, i| {
switch (tree.nodeTag(item)) {
.asm_output => continue,
else => break i,
}
} else info.items.len;
result.outputs = info.items[0..outputs_end];
result.inputs = info.items[outputs_end..];
return result;
}
fn fullWhileComponents(tree: Ast, info: full.While.Components) full.While {
var result: full.While = .{
.ast = info,
@ -2447,6 +2490,14 @@ pub fn fullAsm(tree: Ast, node: Node.Index) ?full.Asm {
};
}
/// To be deleted after 0.15.0 is tagged
pub fn legacyAsm(tree: Ast, node: Node.Index) ?full.AsmLegacy {
return switch (tree.nodeTag(node)) {
.asm_legacy => tree.asmLegacy(node),
else => null,
};
}
pub fn fullCall(tree: Ast, buffer: *[1]Ast.Node.Index, node: Node.Index) ?full.Call {
return switch (tree.nodeTag(node)) {
.call, .call_comma => tree.callFull(node),
@ -2827,6 +2878,21 @@ pub const full = struct {
};
pub const Asm = struct {
ast: Components,
volatile_token: ?TokenIndex,
outputs: []const Node.Index,
inputs: []const Node.Index,
pub const Components = struct {
asm_token: TokenIndex,
template: Node.Index,
items: []const Node.Index,
clobbers: Node.OptionalIndex,
rparen: TokenIndex,
};
};
pub const AsmLegacy = struct {
ast: Components,
volatile_token: ?TokenIndex,
first_clobber: ?TokenIndex,
@ -3833,15 +3899,22 @@ pub const Node = struct {
/// Same as `block` except there is known to be a trailing comma before
/// the final rbrace.
block_semicolon,
/// `asm(lhs)`.
/// `asm(a)`.
///
/// rhs is a `Token.Index` to the `)` token.
/// The `main_token` field is the `asm` token.
asm_simple,
/// `asm(lhs, a)`.
///
/// The `data` field is a `.node_and_extra`:
/// 1. a `Node.Index` to lhs.
/// 2. a `ExtraIndex` to `AsmLegacy`.
///
/// The `main_token` field is the `asm` token.
asm_legacy,
/// `asm(a, b)`.
///
/// The `data` field is a `.node_and_extra`:
/// 1. a `Node.Index` to a.
/// 2. a `ExtraIndex` to `Asm`.
///
/// The `main_token` field is the `asm` token.
@ -4014,9 +4087,18 @@ pub const Node = struct {
callconv_expr: OptionalIndex,
};
/// To be removed after 0.15.0 is tagged
pub const AsmLegacy = struct {
items_start: ExtraIndex,
items_end: ExtraIndex,
/// Needed to make lastToken() work.
rparen: TokenIndex,
};
pub const Asm = struct {
items_start: ExtraIndex,
items_end: ExtraIndex,
clobbers: OptionalIndex,
/// Needed to make lastToken() work.
rparen: TokenIndex,
};

View file

@ -505,6 +505,7 @@ fn lvalExpr(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Ins
.bool_or,
.@"asm",
.asm_simple,
.asm_legacy,
.string_literal,
.number_literal,
.call,
@ -811,6 +812,12 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE
.@"asm",
=> return asmExpr(gz, scope, ri, node, tree.fullAsm(node).?),
.asm_legacy => {
return astgen.failNodeNotes(node, "legacy asm clobbers syntax", .{}, &[_]u32{
try astgen.errNoteNode(node, "use 'zig fmt' to auto-upgrade", .{}),
});
},
.string_literal => return stringLiteral(gz, ri, node),
.multiline_string_literal => return multilineStringLiteral(gz, ri, node),
@ -8774,7 +8781,7 @@ fn asmExpr(
if (is_container_asm) {
if (full.volatile_token) |t|
return astgen.failTok(t, "volatile is meaningless on global assembly", .{});
if (full.outputs.len != 0 or full.inputs.len != 0 or full.first_clobber != null)
if (full.outputs.len != 0 or full.inputs.len != 0 or full.ast.clobbers != .none)
return astgen.failNode(node, "global assembly cannot have inputs, outputs, or clobbers", .{});
} else {
if (full.outputs.len == 0 and full.volatile_token == null) {
@ -8839,32 +8846,12 @@ fn asmExpr(
};
}
var clobbers_buffer: [63]u32 = undefined;
var clobber_i: usize = 0;
if (full.first_clobber) |first_clobber| clobbers: {
// asm ("foo" ::: "a", "b")
// asm ("foo" ::: "a", "b",)
var tok_i = first_clobber;
while (true) : (tok_i += 1) {
if (clobber_i >= clobbers_buffer.len) {
return astgen.failTok(tok_i, "too many asm clobbers", .{});
}
clobbers_buffer[clobber_i] = @intFromEnum((try astgen.strLitAsString(tok_i)).index);
clobber_i += 1;
tok_i += 1;
switch (tree.tokenTag(tok_i)) {
.r_paren => break :clobbers,
.comma => {
if (tree.tokenTag(tok_i + 1) == .r_paren) {
break :clobbers;
} else {
continue;
}
},
else => unreachable,
}
}
}
const clobbers: Zir.Inst.Ref = if (full.ast.clobbers.unwrap()) |clobbers_node|
try comptimeExpr(gz, scope, .{ .rl = .{
.coerced_ty = try gz.addBuiltinValue(clobbers_node, .clobbers),
} }, clobbers_node, .clobber)
else
.none;
const result = try gz.addAsm(.{
.tag = tag_and_tmpl.tag,
@ -8874,7 +8861,7 @@ fn asmExpr(
.output_type_bits = output_type_bits,
.outputs = outputs,
.inputs = inputs,
.clobbers = clobbers_buffer[0..clobber_i],
.clobbers = clobbers,
});
return rvalue(gz, ri, result, node);
}
@ -10332,6 +10319,7 @@ fn nodeMayEvalToError(tree: *const Ast, start_node: Ast.Node.Index) BuiltinFn.Ev
.@"asm",
.asm_simple,
.asm_legacy,
.identifier,
.field_access,
.deref,
@ -10575,6 +10563,7 @@ fn nodeImpliesMoreThanOnePossibleValue(tree: *const Ast, start_node: Ast.Node.In
.tagged_union_enum_tag_trailing,
.@"asm",
.asm_simple,
.asm_legacy,
.add,
.add_wrap,
.add_sat,
@ -10813,6 +10802,7 @@ fn nodeImpliesComptimeOnly(tree: *const Ast, start_node: Ast.Node.Index) bool {
.tagged_union_enum_tag_trailing,
.@"asm",
.asm_simple,
.asm_legacy,
.add,
.add_wrap,
.add_sat,
@ -12806,7 +12796,7 @@ const GenZir = struct {
is_volatile: bool,
outputs: []const Zir.Inst.Asm.Output,
inputs: []const Zir.Inst.Asm.Input,
clobbers: []const u32,
clobbers: Zir.Inst.Ref,
},
) !Zir.Inst.Ref {
const astgen = gz.astgen;
@ -12816,13 +12806,13 @@ const GenZir = struct {
try astgen.instructions.ensureUnusedCapacity(gpa, 1);
try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.Asm).@"struct".fields.len +
args.outputs.len * @typeInfo(Zir.Inst.Asm.Output).@"struct".fields.len +
args.inputs.len * @typeInfo(Zir.Inst.Asm.Input).@"struct".fields.len +
args.clobbers.len);
args.inputs.len * @typeInfo(Zir.Inst.Asm.Input).@"struct".fields.len);
const payload_index = gz.astgen.addExtraAssumeCapacity(Zir.Inst.Asm{
.src_node = gz.nodeIndexToRelative(args.node),
.asm_source = args.asm_source,
.output_type_bits = args.output_type_bits,
.clobbers = args.clobbers,
});
for (args.outputs) |output| {
_ = gz.astgen.addExtraAssumeCapacity(output);
@ -12830,23 +12820,19 @@ const GenZir = struct {
for (args.inputs) |input| {
_ = gz.astgen.addExtraAssumeCapacity(input);
}
gz.astgen.extra.appendSliceAssumeCapacity(args.clobbers);
// * 0b00000000_0000XXXX - `outputs_len`.
// * 0b0000000X_XXXX0000 - `inputs_len`.
// * 0b0XXXXXX0_00000000 - `clobbers_len`.
// * 0bX0000000_00000000 - is volatile
const small: u16 = @as(u16, @as(u4, @intCast(args.outputs.len))) << 0 |
@as(u16, @as(u5, @intCast(args.inputs.len))) << 4 |
@as(u16, @as(u6, @intCast(args.clobbers.len))) << 9 |
@as(u16, @intFromBool(args.is_volatile)) << 15;
const small: Zir.Inst.Asm.Small = .{
.outputs_len = @intCast(args.outputs.len),
.inputs_len = @intCast(args.inputs.len),
.is_volatile = args.is_volatile,
};
const new_index: Zir.Inst.Index = @enumFromInt(astgen.instructions.len);
astgen.instructions.appendAssumeCapacity(.{
.tag = .extended,
.data = .{ .extended = .{
.opcode = args.tag,
.small = small,
.small = @bitCast(small),
.operand = payload_index,
} },
});

View file

@ -310,6 +310,7 @@ fn expr(astrl: *AstRlAnnotate, node: Ast.Node.Index, block: ?*Block, ri: ResultI
.unreachable_literal,
.asm_simple,
.@"asm",
.asm_legacy,
.enum_literal,
.error_value,
.anyframe_literal,

View file

@ -2801,7 +2801,7 @@ fn expectSwitchSuffix(p: *Parse, main_token: TokenIndex) !Node.Index {
///
/// AsmInput <- COLON AsmInputList AsmClobbers?
///
/// AsmClobbers <- COLON StringList
/// AsmClobbers <- COLON Expr
///
/// StringList <- (STRINGLITERAL COMMA)* STRINGLITERAL?
///
@ -2841,7 +2841,8 @@ fn expectAsmExpr(p: *Parse) !Node.Index {
else => try p.warnExpected(.comma),
}
}
if (p.eatToken(.colon)) |_| {
const clobbers: Node.OptionalIndex = if (p.eatToken(.colon)) |_| clobbers: {
while (true) {
const input_item = try p.parseAsmInputItem() orelse break;
try p.scratch.append(p.gpa, input_item);
@ -2853,7 +2854,11 @@ fn expectAsmExpr(p: *Parse) !Node.Index {
else => try p.warnExpected(.comma),
}
}
if (p.eatToken(.colon)) |_| {
_ = p.eatToken(.colon) orelse break :clobbers .none;
// For automatic upgrades; delete after 0.15.0 released.
if (p.tokenTag(p.tok_i) == .string_literal) {
while (p.eatToken(.string_literal)) |_| {
switch (p.tokenTag(p.tok_i)) {
.comma => p.tok_i += 1,
@ -2862,8 +2867,25 @@ fn expectAsmExpr(p: *Parse) !Node.Index {
else => try p.warnExpected(.comma),
}
}
const rparen = try p.expectToken(.r_paren);
const span = try p.listToSpan(p.scratch.items[scratch_top..]);
return p.addNode(.{
.tag = .asm_legacy,
.main_token = asm_token,
.data = .{ .node_and_extra = .{
template,
try p.addExtra(Node.AsmLegacy{
.items_start = span.start,
.items_end = span.end,
.rparen = rparen,
}),
} },
});
}
}
break :clobbers (try p.expectExpr()).toOptional();
} else .none;
const rparen = try p.expectToken(.r_paren);
const span = try p.listToSpan(p.scratch.items[scratch_top..]);
return p.addNode(.{
@ -2874,6 +2896,7 @@ fn expectAsmExpr(p: *Parse) !Node.Index {
try p.addExtra(Node.Asm{
.items_start = span.start,
.items_end = span.end,
.clobbers = clobbers,
.rparen = rparen,
}),
} },

View file

@ -1939,11 +1939,6 @@ pub const Inst = struct {
/// `operand` is payload index to `BinNode`.
builtin_extern,
/// Inline assembly.
/// `small`:
/// * 0b00000000_000XXXXX - `outputs_len`.
/// * 0b000000XX_XXX00000 - `inputs_len`.
/// * 0b0XXXXX00_00000000 - `clobbers_len`.
/// * 0bX0000000_00000000 - is volatile
/// `operand` is payload index to `Asm`.
@"asm",
/// Same as `asm` except the assembly template is not a string literal but a comptime
@ -2495,7 +2490,6 @@ pub const Inst = struct {
/// Trailing:
/// 0. Output for every outputs_len
/// 1. Input for every inputs_len
/// 2. clobber: NullTerminatedString // index into string_bytes (null terminated) for every clobbers_len.
pub const Asm = struct {
src_node: Ast.Node.Offset,
// null-terminated string index
@ -2505,6 +2499,13 @@ pub const Inst = struct {
/// 0b1 - operand is a type; asm expression has the output as the result.
/// 0b0X is the first output, 0bX0 is the second, etc.
output_type_bits: u32,
clobbers: Ref,
pub const Small = packed struct(u16) {
is_volatile: bool,
outputs_len: u7,
inputs_len: u8,
};
pub const Output = struct {
/// index into string_bytes (null terminated)
@ -3482,6 +3483,7 @@ pub const Inst = struct {
extern_options,
type_info,
branch_hint,
clobbers,
// Values
calling_convention_c,
calling_convention_inline,

View file

@ -227,7 +227,7 @@ fn expr(zg: *ZonGen, node: Ast.Node.Index, dest_node: Zoir.Node.Index) Allocator
=> try zg.addErrorNode(node, "control flow is not allowed in ZON", .{}),
.@"comptime" => try zg.addErrorNode(node, "keyword 'comptime' is not allowed in ZON", .{}),
.asm_simple, .@"asm" => try zg.addErrorNode(node, "inline asm is not allowed in ZON", .{}),
.asm_simple, .@"asm", .asm_legacy => try zg.addErrorNode(node, "inline asm is not allowed in ZON", .{}),
.builtin_call_two,
.builtin_call_two_comma,

View file

@ -852,6 +852,9 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
.@"asm",
=> return renderAsm(r, tree.fullAsm(node).?, space),
// To be removed after 0.15.0 is tagged
.asm_legacy => return renderAsmLegacy(r, tree.legacyAsm(node).?, space),
.enum_literal => {
try renderToken(r, tree.nodeMainToken(node) - 1, .none); // .
return renderIdentifier(r, tree.nodeMainToken(node), space, .eagerly_unquote); // name
@ -2363,9 +2366,9 @@ fn renderContainerDecl(
return renderToken(r, rbrace, space); // rbrace
}
fn renderAsm(
fn renderAsmLegacy(
r: *Render,
asm_node: Ast.full.Asm,
asm_node: Ast.full.AsmLegacy,
space: Space,
) Error!void {
const tree = r.tree;
@ -2391,12 +2394,17 @@ fn renderAsm(
try renderToken(r, first_clobber - 2, .none);
try renderToken(r, first_clobber - 1, .space);
try ais.writer().writeAll(".{ ");
var tok_i = first_clobber;
while (true) : (tok_i += 1) {
try renderToken(r, tok_i, .none);
try ais.writer().writeAll(".@");
try ais.writer().writeAll(tokenSliceForRender(tree, tok_i));
tok_i += 1;
switch (tree.tokenTag(tok_i)) {
.r_paren => {
try ais.writer().writeAll(" }");
ais.popIndent();
return renderToken(r, tok_i, space);
},
@ -2412,10 +2420,7 @@ fn renderAsm(
}
}
} else {
// asm ("foo")
try renderExpression(r, asm_node.ast.template, .none);
ais.popIndent();
return renderToken(r, asm_node.ast.rparen, space); // rparen
unreachable;
}
}
@ -2499,13 +2504,18 @@ fn renderAsm(
};
try renderToken(r, colon3, .space); // :
try ais.writer().writeAll(".{ ");
const first_clobber = asm_node.first_clobber.?;
var tok_i = first_clobber;
while (true) {
switch (tree.tokenTag(tok_i + 1)) {
.r_paren => {
ais.setIndentDelta(indent_delta);
try renderToken(r, tok_i, .newline);
try ais.writer().writeAll(".@");
const lexeme = tokenSliceForRender(tree, tok_i);
try ais.writer().writeAll(lexeme);
try ais.writer().writeAll(" }");
try renderSpace(r, tok_i, lexeme.len, .newline);
ais.popIndent();
return renderToken(r, tok_i + 1, space);
},
@ -2513,12 +2523,17 @@ fn renderAsm(
switch (tree.tokenTag(tok_i + 2)) {
.r_paren => {
ais.setIndentDelta(indent_delta);
try renderToken(r, tok_i, .newline);
try ais.writer().writeAll(".@");
const lexeme = tokenSliceForRender(tree, tok_i);
try ais.writer().writeAll(lexeme);
try ais.writer().writeAll(" }");
try renderSpace(r, tok_i, lexeme.len, .newline);
ais.popIndent();
return renderToken(r, tok_i + 2, space);
},
else => {
try renderToken(r, tok_i, .none);
try ais.writer().writeAll(".@");
try ais.writer().writeAll(tokenSliceForRender(tree, tok_i));
try renderToken(r, tok_i + 1, .space);
tok_i += 2;
},
@ -2529,6 +2544,131 @@ fn renderAsm(
}
}
fn renderAsm(
r: *Render,
asm_node: Ast.full.Asm,
space: Space,
) Error!void {
const tree = r.tree;
const ais = r.ais;
try renderToken(r, asm_node.ast.asm_token, .space); // asm
if (asm_node.volatile_token) |volatile_token| {
try renderToken(r, volatile_token, .space); // volatile
try renderToken(r, volatile_token + 1, .none); // lparen
} else {
try renderToken(r, asm_node.ast.asm_token + 1, .none); // lparen
}
if (asm_node.ast.items.len == 0) {
try ais.forcePushIndent(.normal);
if (asm_node.ast.clobbers.unwrap()) |clobbers| {
// asm ("foo" ::: clobbers)
try renderExpression(r, asm_node.ast.template, .space);
// Render the three colons.
const first_clobber = tree.firstToken(clobbers);
try renderToken(r, first_clobber - 3, .none);
try renderToken(r, first_clobber - 2, .none);
try renderToken(r, first_clobber - 1, .space);
try renderExpression(r, clobbers, .none);
ais.popIndent();
return renderToken(r, asm_node.ast.rparen, space); // rparen
}
// asm ("foo")
try renderExpression(r, asm_node.ast.template, .none);
ais.popIndent();
return renderToken(r, asm_node.ast.rparen, space); // rparen
}
try ais.forcePushIndent(.normal);
try renderExpression(r, asm_node.ast.template, .newline);
ais.setIndentDelta(asm_indent_delta);
const colon1 = tree.lastToken(asm_node.ast.template) + 1;
const colon2 = if (asm_node.outputs.len == 0) colon2: {
try renderToken(r, colon1, .newline); // :
break :colon2 colon1 + 1;
} else colon2: {
try renderToken(r, colon1, .space); // :
try ais.forcePushIndent(.normal);
for (asm_node.outputs, 0..) |asm_output, i| {
if (i + 1 < asm_node.outputs.len) {
const next_asm_output = asm_node.outputs[i + 1];
try renderAsmOutput(r, asm_output, .none);
const comma = tree.firstToken(next_asm_output) - 1;
try renderToken(r, comma, .newline); // ,
try renderExtraNewlineToken(r, tree.firstToken(next_asm_output));
} else if (asm_node.inputs.len == 0 and asm_node.ast.clobbers == .none) {
try ais.pushSpace(.comma);
try renderAsmOutput(r, asm_output, .comma);
ais.popSpace();
ais.popIndent();
ais.setIndentDelta(indent_delta);
ais.popIndent();
return renderToken(r, asm_node.ast.rparen, space); // rparen
} else {
try ais.pushSpace(.comma);
try renderAsmOutput(r, asm_output, .comma);
ais.popSpace();
const comma_or_colon = tree.lastToken(asm_output) + 1;
ais.popIndent();
break :colon2 switch (tree.tokenTag(comma_or_colon)) {
.comma => comma_or_colon + 1,
else => comma_or_colon,
};
}
} else unreachable;
};
const colon3 = if (asm_node.inputs.len == 0) colon3: {
try renderToken(r, colon2, .newline); // :
break :colon3 colon2 + 1;
} else colon3: {
try renderToken(r, colon2, .space); // :
try ais.forcePushIndent(.normal);
for (asm_node.inputs, 0..) |asm_input, i| {
if (i + 1 < asm_node.inputs.len) {
const next_asm_input = asm_node.inputs[i + 1];
try renderAsmInput(r, asm_input, .none);
const first_token = tree.firstToken(next_asm_input);
try renderToken(r, first_token - 1, .newline); // ,
try renderExtraNewlineToken(r, first_token);
} else if (asm_node.ast.clobbers == .none) {
try ais.pushSpace(.comma);
try renderAsmInput(r, asm_input, .comma);
ais.popSpace();
ais.popIndent();
ais.setIndentDelta(indent_delta);
ais.popIndent();
return renderToken(r, asm_node.ast.rparen, space); // rparen
} else {
try ais.pushSpace(.comma);
try renderAsmInput(r, asm_input, .comma);
ais.popSpace();
const comma_or_colon = tree.lastToken(asm_input) + 1;
ais.popIndent();
break :colon3 switch (tree.tokenTag(comma_or_colon)) {
.comma => comma_or_colon + 1,
else => comma_or_colon,
};
}
}
unreachable;
};
try renderToken(r, colon3, .space); // :
const clobbers = asm_node.ast.clobbers.unwrap().?;
try renderExpression(r, clobbers, .none);
ais.setIndentDelta(indent_delta);
ais.popIndent();
return renderToken(r, asm_node.ast.rparen, space); // rparen
}
fn renderCall(
r: *Render,
call: Ast.full.Call,

View file

@ -1413,19 +1413,20 @@ pub const ShuffleTwoMask = enum(u32) {
/// terminated string.
/// - name: memory at this position is reinterpreted as a null
/// terminated string. pad to the next u32 after the null byte.
/// 4. for every clobbers_len
/// - clobber_name: memory at this position is reinterpreted as a null
/// terminated string. pad to the next u32 after the null byte.
/// 5. A number of u32 elements follow according to the equation `(source_len + 3) / 4`.
/// 4. A number of u32 elements follow according to the equation `(source_len + 3) / 4`.
/// Memory starting at this position is reinterpreted as the source bytes.
pub const Asm = struct {
/// Length of the assembly source in bytes.
source_len: u32,
outputs_len: u32,
inputs_len: u32,
/// The MSB is `is_volatile`.
/// The rest of the bits are `clobbers_len`.
flags: u32,
/// A comptime `std.builtin.assembly.Clobbers` value for the target architecture.
clobbers: InternPool.Index,
flags: Flags,
pub const Flags = packed struct(u32) {
outputs_len: u31,
is_volatile: bool,
};
};
pub const Cmpxchg = struct {
@ -1749,7 +1750,7 @@ pub fn extraData(air: Air, comptime T: type, index: usize) struct { data: T, end
@field(result, field.name) = switch (field.type) {
u32 => air.extra.items[i],
InternPool.Index, Inst.Ref => @enumFromInt(air.extra.items[i]),
i32, CondBr.BranchHints => @bitCast(air.extra.items[i]),
i32, CondBr.BranchHints, Asm.Flags => @bitCast(air.extra.items[i]),
else => @compileError("bad field type: " ++ @typeName(field.type)),
};
i += 1;

View file

@ -1208,8 +1208,9 @@ fn analyzeInst(
.assembly => {
const extra = a.air.extraData(Air.Asm, inst_datas[@intFromEnum(inst)].ty_pl.payload);
const outputs_len = extra.data.flags.outputs_len;
var extra_i: usize = extra.end;
const outputs = @as([]const Air.Inst.Ref, @ptrCast(a.air.extra.items[extra_i..][0..extra.data.outputs_len]));
const outputs = @as([]const Air.Inst.Ref, @ptrCast(a.air.extra.items[extra_i..][0..outputs_len]));
extra_i += outputs.len;
const inputs = @as([]const Air.Inst.Ref, @ptrCast(a.air.extra.items[extra_i..][0..extra.data.inputs_len]));
extra_i += inputs.len;

View file

@ -366,16 +366,11 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
.assembly => {
const ty_pl = data[@intFromEnum(inst)].ty_pl;
const extra = self.air.extraData(Air.Asm, ty_pl.payload);
const outputs_len = extra.data.flags.outputs_len;
var extra_i = extra.end;
const outputs = @as(
[]const Air.Inst.Ref,
@ptrCast(self.air.extra.items[extra_i..][0..extra.data.outputs_len]),
);
const outputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..outputs_len]);
extra_i += outputs.len;
const inputs = @as(
[]const Air.Inst.Ref,
@ptrCast(self.air.extra.items[extra_i..][0..extra.data.inputs_len]),
);
const inputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..extra.data.inputs_len]);
extra_i += inputs.len;
var bt = self.liveness.iterateBigTomb(inst);

View file

@ -1,5 +1,6 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const build_options = @import("build_options");
const Zcu = @import("../Zcu.zig");
@ -9,7 +10,7 @@ const Air = @import("../Air.zig");
const InternPool = @import("../InternPool.zig");
pub fn write(air: Air, stream: *std.io.Writer, pt: Zcu.PerThread, liveness: ?Air.Liveness) void {
comptime std.debug.assert(build_options.enable_debug_extensions);
comptime assert(build_options.enable_debug_extensions);
const instruction_bytes = air.instructions.len *
// Here we don't use @sizeOf(Air.Inst.Data) because it would include
// the debug safety tag but we want to measure release size.
@ -59,7 +60,7 @@ pub fn writeInst(
pt: Zcu.PerThread,
liveness: ?Air.Liveness,
) void {
comptime std.debug.assert(build_options.enable_debug_extensions);
comptime assert(build_options.enable_debug_extensions);
var writer: Writer = .{
.pt = pt,
.gpa = pt.zcu.gpa,
@ -643,8 +644,8 @@ const Writer = struct {
fn writeAssembly(w: *Writer, s: *std.io.Writer, inst: Air.Inst.Index) Error!void {
const ty_pl = w.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
const extra = w.air.extraData(Air.Asm, ty_pl.payload);
const is_volatile = @as(u1, @truncate(extra.data.flags >> 31)) != 0;
const clobbers_len = @as(u31, @truncate(extra.data.flags));
const is_volatile = extra.data.flags.is_volatile;
const outputs_len = extra.data.flags.outputs_len;
var extra_i: usize = extra.end;
var op_index: usize = 0;
@ -655,7 +656,7 @@ const Writer = struct {
try s.writeAll(", volatile");
}
const outputs = @as([]const Air.Inst.Ref, @ptrCast(w.air.extra.items[extra_i..][0..extra.data.outputs_len]));
const outputs = @as([]const Air.Inst.Ref, @ptrCast(w.air.extra.items[extra_i..][0..outputs_len]));
extra_i += outputs.len;
const inputs = @as([]const Air.Inst.Ref, @ptrCast(w.air.extra.items[extra_i..][0..extra.data.inputs_len]));
extra_i += inputs.len;
@ -695,19 +696,35 @@ const Writer = struct {
try s.writeByte(')');
}
{
var clobber_i: u32 = 0;
while (clobber_i < clobbers_len) : (clobber_i += 1) {
const extra_bytes = std.mem.sliceAsBytes(w.air.extra.items[extra_i..]);
const clobber = std.mem.sliceTo(extra_bytes, 0);
// This equation accounts for the fact that even if we have exactly 4 bytes
// for the string, we still use the next u32 for the null terminator.
extra_i += clobber.len / 4 + 1;
try s.writeAll(", ~{");
try s.writeAll(clobber);
try s.writeAll("}");
}
const zcu = w.pt.zcu;
const ip = &zcu.intern_pool;
const aggregate = ip.indexToKey(extra.data.clobbers).aggregate;
const struct_type: Type = .fromInterned(aggregate.ty);
switch (aggregate.storage) {
.elems => |elems| for (elems, 0..) |elem, i| {
switch (elem) {
.bool_true => {
const clobber = struct_type.structFieldName(i, zcu).toSlice(ip).?;
assert(clobber.len != 0);
try s.writeAll(", ~{");
try s.writeAll(clobber);
try s.writeAll("}");
},
.bool_false => continue,
else => unreachable,
}
},
.repeated_elem => |elem| {
try s.writeAll(", ");
try s.writeAll(switch (elem) {
.bool_true => "<all clobbers>",
.bool_false => "<no clobbers>",
else => unreachable,
});
},
.bytes => |bytes| {
try s.print(", {x}", .{bytes});
},
}
const asm_source = std.mem.sliceAsBytes(w.air.extra.items[extra_i..])[0..extra.data.source_len];
try s.print(", \"{f}\"", .{std.zig.fmtString(asm_source)});

View file

@ -416,8 +416,9 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool {
if (!checkType(data.ty_pl.ty.toType(), zcu)) return false;
// Luckily, we only care about the inputs and outputs, so we don't have to do
// the whole null-terminated string dance.
const outputs: []const Air.Inst.Ref = @ptrCast(air.extra.items[extra.end..][0..extra.data.outputs_len]);
const inputs: []const Air.Inst.Ref = @ptrCast(air.extra.items[extra.end + extra.data.outputs_len ..][0..extra.data.inputs_len]);
const outputs_len = extra.data.flags.outputs_len;
const outputs: []const Air.Inst.Ref = @ptrCast(air.extra.items[extra.end..][0..outputs_len]);
const inputs: []const Air.Inst.Ref = @ptrCast(air.extra.items[extra.end + outputs_len ..][0..extra.data.inputs_len]);
for (outputs) |output| if (output != .none and !checkRef(output, zcu)) return false;
for (inputs) |input| if (input != .none and !checkRef(input, zcu)) return false;
},

View file

@ -54,6 +54,7 @@ namespace_name_deps: std.AutoArrayHashMapUnmanaged(NamespaceNameKey, DepEntry.In
memoized_state_main_deps: DepEntry.Index.Optional,
memoized_state_panic_deps: DepEntry.Index.Optional,
memoized_state_va_list_deps: DepEntry.Index.Optional,
memoized_state_assembly_deps: DepEntry.Index.Optional,
/// Given a `Depender`, points to an entry in `dep_entries` whose `depender`
/// matches. The `next_dependee` field can be used to iterate all such entries
@ -96,6 +97,7 @@ pub const empty: InternPool = .{
.memoized_state_main_deps = .none,
.memoized_state_panic_deps = .none,
.memoized_state_va_list_deps = .none,
.memoized_state_assembly_deps = .none,
.first_dependency = .empty,
.dep_entries = .empty,
.free_dep_entries = .empty,
@ -458,6 +460,8 @@ pub const MemoizedStateStage = enum(u32) {
panic,
/// Specifically `std.builtin.VaList`. See `Zcu.BuiltinDecl.stage`.
va_list,
/// Everything within `std.builtin.assembly`. See `Zcu.BuiltinDecl.stage`.
assembly,
};
pub const ComptimeUnit = extern struct {
@ -880,6 +884,7 @@ pub fn dependencyIterator(ip: *const InternPool, dependee: Dependee) DependencyI
.main => ip.memoized_state_main_deps.unwrap(),
.panic => ip.memoized_state_panic_deps.unwrap(),
.va_list => ip.memoized_state_va_list_deps.unwrap(),
.assembly => ip.memoized_state_assembly_deps.unwrap(),
},
} orelse return .{
.ip = ip,
@ -915,6 +920,7 @@ pub fn addDependency(ip: *InternPool, gpa: Allocator, depender: AnalUnit, depend
.main => &ip.memoized_state_main_deps,
.panic => &ip.memoized_state_panic_deps,
.va_list => &ip.memoized_state_va_list_deps,
.assembly => &ip.memoized_state_assembly_deps,
};
if (deps.unwrap()) |first| {

View file

@ -16413,10 +16413,10 @@ fn zirAsm(
const extra = sema.code.extraData(Zir.Inst.Asm, extended.operand);
const src = block.nodeOffset(extra.data.src_node);
const ret_ty_src = block.src(.{ .node_offset_asm_ret_ty = extra.data.src_node });
const outputs_len: u4 = @truncate(extended.small);
const inputs_len: u5 = @truncate(extended.small >> 4);
const clobbers_len: u6 = @truncate(extended.small >> 9);
const is_volatile = @as(u1, @truncate(extended.small >> 15)) != 0;
const small: Zir.Inst.Asm.Small = @bitCast(extended.small);
const outputs_len = small.outputs_len;
const inputs_len = small.inputs_len;
const is_volatile = small.is_volatile;
const is_global_assembly = sema.func_index == .none;
const zir_tags = sema.code.instructions.items(.tag);
@ -16432,7 +16432,7 @@ fn zirAsm(
if (inputs_len != 0) {
return sema.fail(block, src, "module-level assembly does not support inputs", .{});
}
if (clobbers_len != 0) {
if (extra.data.clobbers != .none) {
return sema.fail(block, src, "module-level assembly does not support clobbers", .{});
}
if (is_volatile) {
@ -16506,15 +16506,11 @@ fn zirAsm(
inputs[arg_i] = .{ .c = constraint, .n = name };
}
const clobbers = try sema.arena.alloc([]const u8, clobbers_len);
for (clobbers) |*name| {
const name_index: Zir.NullTerminatedString = @enumFromInt(sema.code.extra[extra_i]);
name.* = sema.code.nullTerminatedString(name_index);
extra_i += 1;
needed_capacity += name.*.len / 4 + 1;
}
const clobbers = if (extra.data.clobbers == .none) empty: {
const clobbers_ty = try sema.getBuiltinType(src, .@"assembly.Clobbers");
break :empty try sema.structInitEmpty(block, clobbers_ty, src, src);
} else try sema.resolveInst(extra.data.clobbers); // Already coerced by AstGen.
const clobbers_val = try sema.resolveConstDefinedValue(block, src, clobbers, .{ .simple = .clobber });
needed_capacity += (asm_source.len + 3) / 4;
const gpa = sema.gpa;
@ -16525,9 +16521,12 @@ fn zirAsm(
.ty = expr_ty,
.payload = sema.addExtraAssumeCapacity(Air.Asm{
.source_len = @intCast(asm_source.len),
.outputs_len = outputs_len,
.inputs_len = @intCast(args.len),
.flags = (@as(u32, @intFromBool(is_volatile)) << 31) | @as(u32, @intCast(clobbers.len)),
.clobbers = clobbers_val.toIntern(),
.flags = .{
.is_volatile = is_volatile,
.outputs_len = outputs_len,
},
}),
} },
});
@ -16549,12 +16548,6 @@ fn zirAsm(
buffer[input.c.len + 1 + input.n.len] = 0;
sema.air_extra.items.len += (input.c.len + input.n.len + (2 + 3)) / 4;
}
for (clobbers) |clobber| {
const buffer = mem.sliceAsBytes(sema.air_extra.unusedCapacitySlice());
@memcpy(buffer[0..clobber.len], clobber);
buffer[clobber.len] = 0;
sema.air_extra.items.len += clobber.len / 4 + 1;
}
{
const buffer = mem.sliceAsBytes(sema.air_extra.unusedCapacitySlice());
@memcpy(buffer[0..asm_source.len], asm_source);
@ -26197,6 +26190,7 @@ fn zirBuiltinValue(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstD
.extern_options => try sema.getBuiltinType(src, .ExternOptions),
.type_info => try sema.getBuiltinType(src, .Type),
.branch_hint => try sema.getBuiltinType(src, .BranchHint),
.clobbers => try sema.getBuiltinType(src, .@"assembly.Clobbers"),
// zig fmt: on
// Values are handled here.
@ -36546,7 +36540,7 @@ fn payloadToExtraItems(data: anytype) [@typeInfo(@TypeOf(data)).@"struct".fields
inline for (&result, fields) |*val, field| {
val.* = switch (field.type) {
u32 => @field(data, field.name),
i32, Air.CondBr.BranchHints => @bitCast(@field(data, field.name)),
i32, Air.CondBr.BranchHints, Air.Asm.Flags => @bitCast(@field(data, field.name)),
Air.Inst.Ref, InternPool.Index => @intFromEnum(@field(data, field.name)),
else => @compileError("bad field type: " ++ @typeName(field.type)),
};

View file

@ -460,6 +460,9 @@ pub const BuiltinDecl = enum {
VaList,
assembly,
@"assembly.Clobbers",
/// Determines what kind of validation will be done to the decl's value.
pub fn kind(decl: BuiltinDecl) enum { type, func, string } {
return switch (decl) {
@ -480,6 +483,8 @@ pub const BuiltinDecl = enum {
.ExportOptions,
.ExternOptions,
.BranchHint,
.assembly,
.@"assembly.Clobbers",
=> .type,
.Type,
@ -540,6 +545,7 @@ pub const BuiltinDecl = enum {
/// Resolution of these values is done in three distinct stages:
/// * Resolution of `std.builtin.Panic` and everything under it
/// * Resolution of `VaList`
/// * Resolution of `assembly`
/// * Everything else
///
/// Panics are separated because they are provided by the user, so must be able to use
@ -548,14 +554,20 @@ pub const BuiltinDecl = enum {
/// `VaList` is separate because its value depends on the target, so it needs some reflection
/// machinery to work; additionally, it is `@compileError` on some targets, so must be referenced
/// by itself.
///
/// `assembly` is separate because its value depends on the target.
pub fn stage(decl: BuiltinDecl) InternPool.MemoizedStateStage {
if (decl == .VaList) return .va_list;
if (@intFromEnum(decl) <= @intFromEnum(BuiltinDecl.@"Type.Declaration")) {
return .main;
} else {
return .panic;
}
return switch (decl) {
.VaList => .va_list,
.assembly, .@"assembly.Clobbers" => .assembly,
else => {
if (@intFromEnum(decl) <= @intFromEnum(BuiltinDecl.@"Type.Declaration")) {
return .main;
} else {
return .panic;
}
},
};
}
/// Based on the tag name, determines how to access this decl; either as a direct child of the

View file

@ -635,6 +635,7 @@ pub fn ensureMemoizedStateUpToDate(pt: Zcu.PerThread, stage: InternPool.Memoized
.main => .Type,
.panic => .panic,
.va_list => .VaList,
.assembly => .assembly,
};
if (zcu.builtin_decl_values.get(to_check) != .none) return;
}

View file

@ -6047,10 +6047,10 @@ fn airBoolOp(func: *Func, inst: Air.Inst.Index) !void {
fn airAsm(func: *Func, inst: Air.Inst.Index) !void {
const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
const extra = func.air.extraData(Air.Asm, ty_pl.payload);
const clobbers_len: u31 = @truncate(extra.data.flags);
const outputs_len = extra.data.flags.outputs_len;
var extra_i: usize = extra.end;
const outputs: []const Air.Inst.Ref =
@ptrCast(func.air.extra.items[extra_i..][0..extra.data.outputs_len]);
@ptrCast(func.air.extra.items[extra_i..][0..outputs_len]);
extra_i += outputs.len;
const inputs: []const Air.Inst.Ref = @ptrCast(func.air.extra.items[extra_i..][0..extra.data.inputs_len]);
extra_i += inputs.len;
@ -6161,21 +6161,33 @@ fn airAsm(func: *Func, inst: Air.Inst.Index) !void {
args.appendAssumeCapacity(arg_mcv);
}
{
var clobber_i: u32 = 0;
while (clobber_i < clobbers_len) : (clobber_i += 1) {
const clobber = std.mem.sliceTo(std.mem.sliceAsBytes(func.air.extra.items[extra_i..]), 0);
// This equation accounts for the fact that even if we have exactly 4 bytes
// for the string, we still use the next u32 for the null terminator.
extra_i += clobber.len / 4 + 1;
if (std.mem.eql(u8, clobber, "") or std.mem.eql(u8, clobber, "memory")) {
// nothing really to do
} else {
try func.register_manager.getReg(parseRegName(clobber) orelse
return func.fail("invalid clobber: '{s}'", .{clobber}), null);
const zcu = func.pt.zcu;
const ip = &zcu.intern_pool;
const aggregate = ip.indexToKey(extra.data.clobbers).aggregate;
const struct_type: Type = .fromInterned(aggregate.ty);
switch (aggregate.storage) {
.elems => |elems| for (elems, 0..) |elem, i| {
switch (elem) {
.bool_true => {
const clobber = struct_type.structFieldName(i, zcu).toSlice(ip).?;
assert(clobber.len != 0);
if (std.mem.eql(u8, clobber, "memory")) {
// nothing really to do
} else {
try func.register_manager.getReg(parseRegName(clobber) orelse
return func.fail("invalid clobber: '{s}'", .{clobber}), null);
}
},
.bool_false => continue,
else => unreachable,
}
}
},
.repeated_elem => |elem| switch (elem) {
.bool_true => @panic("TODO"),
.bool_false => {},
else => unreachable,
},
.bytes => @panic("TODO"),
}
const Label = struct {

View file

@ -873,10 +873,10 @@ fn airArrayToSlice(self: *Self, inst: Air.Inst.Index) !void {
fn airAsm(self: *Self, inst: Air.Inst.Index) !void {
const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
const extra = self.air.extraData(Air.Asm, ty_pl.payload);
const is_volatile = (extra.data.flags & 0x80000000) != 0;
const clobbers_len: u31 = @truncate(extra.data.flags);
const is_volatile = extra.data.flags.is_volatile;
const outputs_len = extra.data.flags.outputs_len;
var extra_i: usize = extra.end;
const outputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i .. extra_i + extra.data.outputs_len]);
const outputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i .. extra_i + outputs_len]);
extra_i += outputs.len;
const inputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i .. extra_i + extra.data.inputs_len]);
extra_i += inputs.len;
@ -921,17 +921,8 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void {
try self.genSetReg(self.typeOf(input), reg, arg_mcv);
}
{
var clobber_i: u32 = 0;
while (clobber_i < clobbers_len) : (clobber_i += 1) {
const clobber = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra.items[extra_i..]), 0);
// This equation accounts for the fact that even if we have exactly 4 bytes
// for the string, we still use the next u32 for the null terminator.
extra_i += clobber.len / 4 + 1;
// TODO honor these
}
}
// TODO honor the clobbers
_ = extra.data.clobbers;
const asm_source = std.mem.sliceAsBytes(self.air.extra.items[extra_i..])[0..extra.data.source_len];

View file

@ -179788,9 +179788,9 @@ fn airAsm(self: *CodeGen, inst: Air.Inst.Index) !void {
const zcu = pt.zcu;
const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
const extra = self.air.extraData(Air.Asm, ty_pl.payload);
const clobbers_len: u31 = @truncate(extra.data.flags);
const outputs_len = extra.data.flags.outputs_len;
var extra_i: usize = extra.end;
const outputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..extra.data.outputs_len]);
const outputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..outputs_len]);
extra_i += outputs.len;
const inputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..extra.data.inputs_len]);
extra_i += inputs.len;
@ -179981,30 +179981,42 @@ fn airAsm(self: *CodeGen, inst: Air.Inst.Index) !void {
args.appendAssumeCapacity(arg_mcv);
}
{
var clobber_i: u32 = 0;
while (clobber_i < clobbers_len) : (clobber_i += 1) {
const clobber = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra.items[extra_i..]), 0);
// This equation accounts for the fact that even if we have exactly 4 bytes
// for the string, we still use the next u32 for the null terminator.
extra_i += clobber.len / 4 + 1;
const ip = &zcu.intern_pool;
const aggregate = ip.indexToKey(extra.data.clobbers).aggregate;
const struct_type: Type = .fromInterned(aggregate.ty);
switch (aggregate.storage) {
.elems => |elems| for (elems, 0..) |elem, i| switch (elem) {
.bool_true => {
const clobber = struct_type.structFieldName(i, zcu).toSlice(ip).?;
assert(clobber.len != 0);
if (std.mem.eql(u8, clobber, "") or std.mem.eql(u8, clobber, "memory") or
std.mem.eql(u8, clobber, "fpsr") or std.mem.eql(u8, clobber, "fpcr") or
std.mem.eql(u8, clobber, "mxcsr") or std.mem.eql(u8, clobber, "dirflag"))
{
// ok, sure
} else if (std.mem.eql(u8, clobber, "cc") or
std.mem.eql(u8, clobber, "flags") or
std.mem.eql(u8, clobber, "eflags") or
std.mem.eql(u8, clobber, "rflags"))
{
try self.spillEflagsIfOccupied();
} else {
try self.register_manager.getReg(parseRegName(clobber) orelse
return self.fail("invalid clobber: '{s}'", .{clobber}), null);
}
}
if (std.mem.eql(u8, clobber, "memory") or
std.mem.eql(u8, clobber, "fpsr") or
std.mem.eql(u8, clobber, "fpcr") or
std.mem.eql(u8, clobber, "mxcsr") or
std.mem.eql(u8, clobber, "dirflag"))
{
// ok, sure
} else if (std.mem.eql(u8, clobber, "cc") or
std.mem.eql(u8, clobber, "flags") or
std.mem.eql(u8, clobber, "eflags") or
std.mem.eql(u8, clobber, "rflags"))
{
try self.spillEflagsIfOccupied();
} else {
try self.register_manager.getReg(parseRegName(clobber) orelse
return self.fail("invalid clobber: '{s}'", .{clobber}), null);
}
},
.bool_false => continue,
else => unreachable,
},
.repeated_elem => |elem| switch (elem) {
.bool_true => @panic("TODO"),
.bool_false => {},
else => unreachable,
},
.bytes => @panic("TODO"),
}
const Label = struct {

View file

@ -5545,11 +5545,11 @@ fn airAsm(f: *Function, inst: Air.Inst.Index) !CValue {
const zcu = pt.zcu;
const ty_pl = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
const extra = f.air.extraData(Air.Asm, ty_pl.payload);
const is_volatile = @as(u1, @truncate(extra.data.flags >> 31)) != 0;
const clobbers_len: u31 = @truncate(extra.data.flags);
const is_volatile = extra.data.flags.is_volatile;
const outputs_len = extra.data.flags.outputs_len;
const gpa = f.object.dg.gpa;
var extra_i: usize = extra.end;
const outputs: []const Air.Inst.Ref = @ptrCast(f.air.extra.items[extra_i..][0..extra.data.outputs_len]);
const outputs: []const Air.Inst.Ref = @ptrCast(f.air.extra.items[extra_i..][0..outputs_len]);
extra_i += outputs.len;
const inputs: []const Air.Inst.Ref = @ptrCast(f.air.extra.items[extra_i..][0..extra.data.inputs_len]);
extra_i += inputs.len;
@ -5645,12 +5645,6 @@ fn airAsm(f: *Function, inst: Air.Inst.Index) !CValue {
try f.object.newline();
}
}
for (0..clobbers_len) |_| {
const clobber = mem.sliceTo(mem.sliceAsBytes(f.air.extra.items[extra_i..]), 0);
// This equation accounts for the fact that even if we have exactly 4 bytes
// for the string, we still use the next u32 for the null terminator.
extra_i += clobber.len / 4 + 1;
}
{
const asm_source = mem.sliceAsBytes(f.air.extra.items[extra_i..])[0..extra.data.source_len];
@ -5757,17 +5751,28 @@ fn airAsm(f: *Function, inst: Air.Inst.Index) !CValue {
try w.writeByte(')');
}
try w.writeByte(':');
for (0..clobbers_len) |clobber_i| {
const clobber = mem.sliceTo(mem.sliceAsBytes(f.air.extra.items[extra_i..]), 0);
// This equation accounts for the fact that even if we have exactly 4 bytes
// for the string, we still use the next u32 for the null terminator.
extra_i += clobber.len / 4 + 1;
if (clobber.len == 0) continue;
if (clobber_i > 0) try w.writeByte(',');
try w.print(" {f}", .{fmtStringLiteral(clobber, null)});
const ip = &zcu.intern_pool;
const aggregate = ip.indexToKey(extra.data.clobbers).aggregate;
const struct_type: Type = .fromInterned(aggregate.ty);
switch (aggregate.storage) {
.elems => |elems| for (elems, 0..) |elem, i| switch (elem) {
.bool_true => {
const name = struct_type.structFieldName(i, zcu).toSlice(ip).?;
assert(name.len != 0);
try w.print(" {f}", .{fmtStringLiteral(name, null)});
(try w.writableArray(1))[0] = ',';
},
.bool_false => continue,
else => unreachable,
},
.repeated_elem => |elem| switch (elem) {
.bool_true => @panic("TODO"),
.bool_false => {},
else => unreachable,
},
.bytes => @panic("TODO"),
}
w.undo(1); // erase the last comma
try w.writeAll(");");
try f.object.newline();

View file

@ -7241,19 +7241,20 @@ pub const FuncGen = struct {
const o = self.ng.object;
const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
const extra = self.air.extraData(Air.Asm, ty_pl.payload);
const is_volatile = @as(u1, @truncate(extra.data.flags >> 31)) != 0;
const clobbers_len: u31 = @truncate(extra.data.flags);
const is_volatile = extra.data.flags.is_volatile;
const outputs_len = extra.data.flags.outputs_len;
const gpa = self.gpa;
var extra_i: usize = extra.end;
const outputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..extra.data.outputs_len]);
const outputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..outputs_len]);
extra_i += outputs.len;
const inputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..extra.data.inputs_len]);
extra_i += inputs.len;
var llvm_constraints: std.ArrayListUnmanaged(u8) = .empty;
defer llvm_constraints.deinit(self.gpa);
defer llvm_constraints.deinit(gpa);
var arena_allocator = std.heap.ArenaAllocator.init(self.gpa);
var arena_allocator = std.heap.ArenaAllocator.init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
@ -7290,7 +7291,7 @@ pub const FuncGen = struct {
// for the string, we still use the next u32 for the null terminator.
extra_i += (constraint.len + name.len + (2 + 3)) / 4;
try llvm_constraints.ensureUnusedCapacity(self.gpa, constraint.len + 3);
try llvm_constraints.ensureUnusedCapacity(gpa, constraint.len + 3);
if (total_i != 0) {
llvm_constraints.appendAssumeCapacity(',');
}
@ -7399,7 +7400,7 @@ pub const FuncGen = struct {
}
}
try llvm_constraints.ensureUnusedCapacity(self.gpa, constraint.len + 1);
try llvm_constraints.ensureUnusedCapacity(gpa, constraint.len + 1);
if (total_i != 0) {
llvm_constraints.appendAssumeCapacity(',');
}
@ -7456,7 +7457,7 @@ pub const FuncGen = struct {
llvm_param_types[llvm_param_i] = llvm_elem_ty;
}
try llvm_constraints.print(self.gpa, ",{d}", .{output_index});
try llvm_constraints.print(gpa, ",{d}", .{output_index});
// In the case of indirect inputs, LLVM requires the callsite to have
// an elementtype(<ty>) attribute.
@ -7466,24 +7467,41 @@ pub const FuncGen = struct {
total_i += 1;
}
{
var clobber_i: u32 = 0;
while (clobber_i < clobbers_len) : (clobber_i += 1) {
const clobber = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra.items[extra_i..]), 0);
// This equation accounts for the fact that even if we have exactly 4 bytes
// for the string, we still use the next u32 for the null terminator.
extra_i += clobber.len / 4 + 1;
const ip = &zcu.intern_pool;
const aggregate = ip.indexToKey(extra.data.clobbers).aggregate;
const struct_type: Type = .fromInterned(aggregate.ty);
switch (aggregate.storage) {
.elems => |elems| for (elems, 0..) |elem, i| {
switch (elem) {
.bool_true => {
const name = struct_type.structFieldName(i, zcu).toSlice(ip).?;
try llvm_constraints.ensureUnusedCapacity(gpa, name.len + 4);
if (total_i != 0) llvm_constraints.appendAssumeCapacity(',');
llvm_constraints.appendSliceAssumeCapacity("~{");
llvm_constraints.appendSliceAssumeCapacity(name);
llvm_constraints.appendSliceAssumeCapacity("}");
try llvm_constraints.ensureUnusedCapacity(self.gpa, clobber.len + 4);
if (total_i != 0) {
llvm_constraints.appendAssumeCapacity(',');
total_i += 1;
},
.bool_false => continue,
else => unreachable,
}
llvm_constraints.appendSliceAssumeCapacity("~{");
llvm_constraints.appendSliceAssumeCapacity(clobber);
llvm_constraints.appendSliceAssumeCapacity("}");
},
.repeated_elem => |elem| switch (elem) {
.bool_true => for (0..struct_type.structFieldCount(zcu)) |i| {
const name = struct_type.structFieldName(i, zcu).toSlice(ip).?;
try llvm_constraints.ensureUnusedCapacity(gpa, name.len + 4);
if (total_i != 0) llvm_constraints.appendAssumeCapacity(',');
llvm_constraints.appendSliceAssumeCapacity("~{");
llvm_constraints.appendSliceAssumeCapacity(name);
llvm_constraints.appendSliceAssumeCapacity("}");
total_i += 1;
}
total_i += 1;
},
.bool_false => {},
else => unreachable,
},
.bytes => @panic("TODO"),
}
// We have finished scanning through all inputs/outputs, so the number of
@ -7497,13 +7515,13 @@ pub const FuncGen = struct {
// to be buggy and regress often.
switch (target.cpu.arch) {
.x86_64, .x86 => {
if (total_i != 0) try llvm_constraints.append(self.gpa, ',');
try llvm_constraints.appendSlice(self.gpa, "~{dirflag},~{fpsr},~{flags}");
if (total_i != 0) try llvm_constraints.append(gpa, ',');
try llvm_constraints.appendSlice(gpa, "~{dirflag},~{fpsr},~{flags}");
total_i += 3;
},
.mips, .mipsel, .mips64, .mips64el => {
if (total_i != 0) try llvm_constraints.append(self.gpa, ',');
try llvm_constraints.appendSlice(self.gpa, "~{$1}");
if (total_i != 0) try llvm_constraints.append(gpa, ',');
try llvm_constraints.appendSlice(gpa, "~{$1}");
total_i += 1;
},
else => {},
@ -7512,7 +7530,7 @@ pub const FuncGen = struct {
const asm_source = std.mem.sliceAsBytes(self.air.extra.items[extra_i..])[0..extra.data.source_len];
// hackety hacks until stage2 has proper inline asm in the frontend.
var rendered_template = std.ArrayList(u8).init(self.gpa);
var rendered_template = std.ArrayList(u8).init(gpa);
defer rendered_template.deinit();
const State = enum { start, percent, input, modifier };

View file

@ -6387,13 +6387,13 @@ const NavGen = struct {
const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
const extra = self.air.extraData(Air.Asm, ty_pl.payload);
const is_volatile = @as(u1, @truncate(extra.data.flags >> 31)) != 0;
const clobbers_len: u31 = @truncate(extra.data.flags);
const is_volatile = extra.data.flags.is_volatile;
const outputs_len = extra.data.flags.outputs_len;
if (!is_volatile and self.liveness.isUnused(inst)) return null;
var extra_i: usize = extra.end;
const outputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..extra.data.outputs_len]);
const outputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..outputs_len]);
extra_i += outputs.len;
const inputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..extra.data.inputs_len]);
extra_i += inputs.len;
@ -6402,7 +6402,7 @@ const NavGen = struct {
return self.todo("implement inline asm with more than 1 output", .{});
}
var as = SpvAssembler{
var as: SpvAssembler = .{
.gpa = self.gpa,
.spv = self.spv,
.func = &self.func,
@ -6486,14 +6486,8 @@ const NavGen = struct {
}
}
{
var clobber_i: u32 = 0;
while (clobber_i < clobbers_len) : (clobber_i += 1) {
const clobber = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra.items[extra_i..]), 0);
extra_i += clobber.len / 4 + 1;
// TODO: Record clobber and use it somewhere.
}
}
// TODO: do something with clobbers
_ = extra.data.clobbers;
const asm_source = std.mem.sliceAsBytes(self.air.extra.items[extra_i..])[0..extra.data.source_len];