Skip to content

feat: parser-level support for block literals and __block variables#971

Open
dotcarmen wants to merge 5 commits into
Vexu:masterfrom
dotcarmen:dotcarmen/block-literals
Open

feat: parser-level support for block literals and __block variables#971
dotcarmen wants to merge 5 commits into
Vexu:masterfrom
dotcarmen:dotcarmen/block-literals

Conversation

@dotcarmen

@dotcarmen dotcarmen commented Feb 26, 2026

Copy link
Copy Markdown
Contributor

blocked on/ part 2 of #969, wip towards closing #825

This PR implements parsing of block literals according to the language extension specification by Clang. Caveat: while the spec document specifies that the parameter list may only be omitted when the return type is omitted, Clang actually compiles programs that omit the return type but not the parameter list (excuse the linker error). The behavior here matches that behavior

Additionally, this change makes no attempt to track __block variable references within block bodies, since that's not necessary for the parse to complete, and that extra information would not be used anywhere yet. in the future, when implementing lowering support for blocks, this information tracking can be added

@dotcarmen dotcarmen force-pushed the dotcarmen/block-literals branch 2 times, most recently from f0b3a01 to 4a15846 Compare February 27, 2026 01:06
@dotcarmen dotcarmen marked this pull request as ready for review February 27, 2026 01:27
@dotcarmen

This comment was marked as resolved.

@dotcarmen

This comment was marked as outdated.

@dotcarmen dotcarmen marked this pull request as draft February 27, 2026 02:14
@dotcarmen dotcarmen force-pushed the dotcarmen/block-literals branch 10 times, most recently from a82f534 to 51c20b7 Compare February 28, 2026 15:54
@dotcarmen dotcarmen marked this pull request as ready for review February 28, 2026 15:54
@dotcarmen

dotcarmen commented Feb 28, 2026

Copy link
Copy Markdown
Contributor Author

i just realized aro doesn't support lowering to aarch64-macos - since macos is currently the only platform that works for testing blocks, i think this is enough to mark Blocks support as "completed" with the caveat that blocks don't get lowered, and an additional ticket should be opened for lowering literals and calls.

@dotcarmen dotcarmen mentioned this pull request Feb 28, 2026
12 tasks
@dotcarmen dotcarmen force-pushed the dotcarmen/block-literals branch 2 times, most recently from 22a3bdb to 201fca6 Compare March 4, 2026 14:42
@dotcarmen dotcarmen force-pushed the dotcarmen/block-literals branch from 201fca6 to a66ae0c Compare March 18, 2026 00:55
@dotcarmen dotcarmen force-pushed the dotcarmen/block-literals branch 2 times, most recently from a295122 to 4f224ce Compare March 25, 2026 22:08
dotcarmen

This comment was marked as outdated.

@dotcarmen dotcarmen force-pushed the dotcarmen/block-literals branch 4 times, most recently from 73b3cad to c9e93a3 Compare March 28, 2026 12:33
@dotcarmen dotcarmen force-pushed the dotcarmen/block-literals branch 6 times, most recently from 2e2bc28 to fad7906 Compare June 14, 2026 02:19
Comment thread src/aro/Attribute.zig Outdated
.weakref,
.zero_call_used_regs,
=> |t| try p.err(tok, .attribute_todo, .{ @tagName(t), "functions" }),
// TODO: clang doesn't emit a dx here, but behavior for `__block <function>` is unspecified

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i wonder if this might apply __block to the variable when initialized with this

eg for auto or id it could be eg id b = ^__block {}; instantiates b as a __block variable

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looked into this by checking -dump-ast (no useful info besides "my hypothesis above is incorrect") and by glancing at the LLVM discourse a bit, couldn't find anything.

this is probably only a detail when lowering, the question is - should i remove this TODO? if clang truly doesn't do anything here, then i think an attribute ignored warning would be nice when not --emulate=clang. but if it does somehow affect compilation, then such a warning would be frivolous

Comment thread src/aro/Attribute.zig Outdated
},

.sentinel,
.blocks, // TODO: clang doesn't emit any diagnostic, but it's not clear what this does

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread src/aro/Parser.zig Outdated
@dotcarmen dotcarmen force-pushed the dotcarmen/block-literals branch from fad7906 to 5cbbdea Compare June 15, 2026 01:08
@dotcarmen dotcarmen requested a review from Vexu June 15, 2026 01:13
Comment thread src/aro/Parser.zig Outdated
Comment thread src/aro/Parser.zig
return .{
.node = node,
.qt = block_type,
.val = try .block(@intFromEnum(node), p.comp),

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does a block value need to exist? The only place where you handle it you explicitly ignore it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

technically it doesn't need to exist. i just created it

  1. to satisfy the return type of fn primaryExpr
  2. because it'd be needed when lowering

i can remove it and leave the value as undefined?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see why you would need it when lowering since the same information is available from the node. You can just leave the value empty for non compile time known values.

Comment thread src/aro/Parser.zig Outdated
}
} else {
try some.coerce(p, ret_qt, e_tok, .ret);
block.return_type = if (ret_expr) |*some| some.qt else .void;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing lvalue conversion:

__auto_type a = ^(int a) {
    int arr[2];
    return arr;
};
Suggested change
block.return_type = if (ret_expr) |*some| some.qt else .void;
} else if (ret_expr) |*some| {
try some.lvalConversion(p, e_tok);
block.return_type = some.qt;
} else {
block.return_type = .void

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang warns about returning a pointer to a value on the stack. i couldn't find a similar diagnostic, so i'm assuming such detection is unimplemented.

@dotcarmen dotcarmen force-pushed the dotcarmen/block-literals branch from 7cf3ccd to f05a42a Compare June 23, 2026 19:21
@dotcarmen dotcarmen force-pushed the dotcarmen/block-literals branch from f05a42a to d985071 Compare June 29, 2026 00:11

@dotcarmen dotcarmen left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still have to finish handling the attributes that i handled before the attributes rework, and finish addressing previous review comments. attributes rework was nice - only had to fix 1 bug in an unused codepath.

the biggest change from the merge: i updated p.block to use a Node with a QualType, both of which get updated as more block literal information is parsed. this allows p.tree.attr_map.hasAttribute to work as expected.

Comment thread src/aro/Attribute/Wip.zig
return node.decl_ref_expr;
}
if (@typeInfo(@TypeOf(Wanted)) == .@"enum" and Wanted.opts.enum_kind == .identifier and node == .identifier_arg) {
if (@typeInfo(Wanted) == .@"enum" and Wanted.opts.enum_kind == .identifier and node == .identifier_arg) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug fix for this previously-unused code path

@dotcarmen dotcarmen force-pushed the dotcarmen/block-literals branch from d985071 to e2cfd6d Compare June 30, 2026 17:32
Comment thread test/cases/block literals.c Outdated
Comment on lines +99 to +116
block literals.c:23:17: error: __block attribute not allowed, only allowed on local variables
block literals.c:23:17: warning: 'blocks' attribute only applies to local variables

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some of the attribute diagnostics emitted by clang are errors, not warnings - but currently only warnings are emitted. i'm assuming this is fine?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't have to use checkTarget if you want to add a more specific diagnostic.

@dotcarmen dotcarmen force-pushed the dotcarmen/block-literals branch from e2cfd6d to 7e599a7 Compare June 30, 2026 18:20
@dotcarmen dotcarmen requested a review from Vexu June 30, 2026 18:20
Comment thread src/aro/Parser.zig

const attr_state = p.wip_attrs.state(true);
defer p.wip_attrs.restore(attr_state);
try p.attributeSpecifier();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should keyword attributes be handled as part of attributeSpecifier?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As in __cdecl? Those are available via msTypeAttribute.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as in bare noinline, _Noreturn, etc - the keywords handled directly in fn declSpec

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those shouldn't be allowed here.

@dotcarmen

Copy link
Copy Markdown
Contributor Author

btw i'm working on splitting out my merge commit to better show the updated blocks-related handling. i'll try to push it tonight

@dotcarmen dotcarmen force-pushed the dotcarmen/block-literals branch from 7e599a7 to c8af2f8 Compare July 2, 2026 20:23

@Vexu Vexu left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's a diff to prevent a crash when using -fblocks without -fsyntax-only:

diff --git a/src/aro/CodeGen.zig b/src/aro/CodeGen.zig
index 082f41cf..4b322540 100644
--- a/src/aro/CodeGen.zig
+++ b/src/aro/CodeGen.zig
@@ -876,6 +876,7 @@ fn genExpr(c: *CodeGen, node_index: Node.Index) Error!Ir.Ref {
         .sizeof_expr,
         .builtin_va_arg_pack,
         .builtin_va_arg_pack_len,
+        .block_literal,
         => return c.fail("TODO CodeGen.genExpr {t}\n", .{node}),
         .codegen_diagnostic => |diagnostic| {
             try c.comp.diagnostics.addWithLocation(c.comp, .{

Comment thread src/aro/Attribute/Map.zig
Comment thread src/aro/Attribute/Wip.zig
wip.current.attr = attr;
wip.current.arg_i = 0;

if (is_block_literal and attr.syntax != .gnu) {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I preferred when you added this error in the parser.

Comment thread src/aro/Attribute/Wip.zig
Comment thread test/cases/block literals.c Outdated
Comment on lines +99 to +116
block literals.c:23:17: error: __block attribute not allowed, only allowed on local variables
block literals.c:23:17: warning: 'blocks' attribute only applies to local variables

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't have to use checkTarget if you want to add a more specific diagnostic.

Comment thread src/aro/Parser.zig
return .{
.node = node,
.qt = block_type,
.val = try .block(@intFromEnum(node), p.comp),

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see why you would need it when lowering since the same information is available from the node. You can just leave the value empty for non compile time known values.

Comment thread src/aro/Parser.zig
if (ret_qt.isBlockLiteralAutoReturn()) {
func_ty.return_type = .void;
try block.qt.set(p.comp, .{ .func = func_ty });
} else if (last_noreturn != .yes) {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing isInvalid() check.

Comment thread src/aro/Parser.zig
if (try p.typeName()) |typename| {
if (typename.is(p.comp, .func)) {
qt = typename;
} else {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs a check for array types:

^int [2] {};

Comment thread src/aro/Parser.zig

var compound_stmt_state: StmtExprState = .{};
const body = try p.compoundStmt(true, &compound_stmt_state) orelse {
try p.err(p.tok_i, .missing_block_literal_body, .{});

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be fatal, the note is never added because of it.

Comment thread src/aro/Parser.zig
Comment thread src/aro/Parser.zig
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants