compiler: make @export take a pointer

Resolves: #14911
This commit is contained in:
mlugg 2024-08-26 01:14:33 +01:00
parent 492cc2ef8d
commit f2d7096bb9
No known key found for this signature in database
GPG Key ID: 3F5B7DCCBF4AF02E
4 changed files with 65 additions and 180 deletions

View File

@ -2861,7 +2861,6 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
.ensure_result_non_error,
.ensure_err_union_payload_void,
.@"export",
.export_value,
.set_eval_branch_quota,
.atomic_store,
.store_node,
@ -9249,87 +9248,11 @@ fn builtinCall(
// zig fmt: on
.@"export" => {
const exported = try expr(gz, scope, .{ .rl = .none }, params[0]);
const export_options_ty = try gz.addBuiltinValue(node, .export_options);
const node_tags = tree.nodes.items(.tag);
const node_datas = tree.nodes.items(.data);
// This function causes a Decl to be exported. The first parameter is not an expression,
// but an identifier of the Decl to be exported.
var namespace: Zir.Inst.Ref = .none;
var decl_name: Zir.NullTerminatedString = .empty;
switch (node_tags[params[0]]) {
.identifier => {
const ident_token = main_tokens[params[0]];
if (isPrimitive(tree.tokenSlice(ident_token))) {
return astgen.failTok(ident_token, "unable to export primitive value", .{});
}
decl_name = try astgen.identAsString(ident_token);
var s = scope;
var found_already: ?Ast.Node.Index = null; // we have found a decl with the same name already
while (true) switch (s.tag) {
.local_val => {
const local_val = s.cast(Scope.LocalVal).?;
if (local_val.name == decl_name) {
local_val.used = ident_token;
_ = try gz.addPlNode(.export_value, node, Zir.Inst.ExportValue{
.operand = local_val.inst,
.options = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = export_options_ty } }, params[1]),
});
return rvalue(gz, ri, .void_value, node);
}
s = local_val.parent;
},
.local_ptr => {
const local_ptr = s.cast(Scope.LocalPtr).?;
if (local_ptr.name == decl_name) {
if (!local_ptr.maybe_comptime)
return astgen.failNode(params[0], "unable to export runtime-known value", .{});
local_ptr.used = ident_token;
const loaded = try gz.addUnNode(.load, local_ptr.ptr, node);
_ = try gz.addPlNode(.export_value, node, Zir.Inst.ExportValue{
.operand = loaded,
.options = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = export_options_ty } }, params[1]),
});
return rvalue(gz, ri, .void_value, node);
}
s = local_ptr.parent;
},
.gen_zir => s = s.cast(GenZir).?.parent,
.defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent,
.namespace => {
const ns = s.cast(Scope.Namespace).?;
if (ns.decls.get(decl_name)) |i| {
if (found_already) |f| {
return astgen.failNodeNotes(node, "ambiguous reference", .{}, &.{
try astgen.errNoteNode(f, "declared here", .{}),
try astgen.errNoteNode(i, "also declared here", .{}),
});
}
// We found a match but must continue looking for ambiguous references to decls.
found_already = i;
}
s = ns.parent;
},
.top => break,
};
if (found_already == null) {
const ident_name = try astgen.identifierTokenString(ident_token);
return astgen.failNode(params[0], "use of undeclared identifier '{s}'", .{ident_name});
}
},
.field_access => {
const namespace_node = node_datas[params[0]].lhs;
namespace = try typeExpr(gz, scope, namespace_node);
const dot_token = main_tokens[params[0]];
const field_ident = dot_token + 1;
decl_name = try astgen.identAsString(field_ident);
},
else => return astgen.failNode(params[0], "symbol to export must identify a declaration", .{}),
}
const options = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = export_options_ty } }, params[1]);
_ = try gz.addPlNode(.@"export", node, Zir.Inst.Export{
.namespace = namespace,
.decl_name = decl_name,
.exported = exported,
.options = options,
});
return rvalue(gz, ri, .void_value, node);

View File

@ -431,14 +431,9 @@ pub const Inst = struct {
error_union_type,
/// `error.Foo` syntax. Uses the `str_tok` field of the Data union.
error_value,
/// Implements the `@export` builtin function, based on either an identifier to a Decl,
/// or field access of a Decl. The thing being exported is the Decl.
/// Implements the `@export` builtin function.
/// Uses the `pl_node` union field. Payload is `Export`.
@"export",
/// Implements the `@export` builtin function, based on a comptime-known value.
/// The thing being exported is the comptime-known value which is the operand.
/// Uses the `pl_node` union field. Payload is `ExportValue`.
export_value,
/// Given a pointer to a struct or object that contains virtual fields, returns a pointer
/// to the named field. The field name is stored in string_bytes. Used by a.b syntax.
/// Uses `pl_node` field. The AST node is the a.b syntax. Payload is Field.
@ -1093,7 +1088,6 @@ pub const Inst = struct {
.ensure_result_non_error,
.ensure_err_union_payload_void,
.@"export",
.export_value,
.field_ptr,
.field_val,
.field_ptr_named,
@ -1314,7 +1308,6 @@ pub const Inst = struct {
.validate_deref,
.validate_destructure,
.@"export",
.export_value,
.set_runtime_safety,
.memcpy,
.memset,
@ -1637,7 +1630,6 @@ pub const Inst = struct {
.error_union_type = .pl_node,
.error_value = .str_tok,
.@"export" = .pl_node,
.export_value = .pl_node,
.field_ptr = .pl_node,
.field_val = .pl_node,
.field_ptr_named = .pl_node,
@ -3425,17 +3417,7 @@ pub const Inst = struct {
};
pub const Export = struct {
/// If present, this is referring to a Decl via field access, e.g. `a.b`.
/// If omitted, this is referring to a Decl via identifier, e.g. `a`.
namespace: Ref,
/// Null-terminated string index.
decl_name: NullTerminatedString,
options: Ref,
};
pub const ExportValue = struct {
/// The comptime value to export.
operand: Ref,
exported: Ref,
options: Ref,
};
@ -3793,7 +3775,6 @@ fn findDeclsInner(
.error_union_type,
.error_value,
.@"export",
.export_value,
.field_ptr,
.field_val,
.field_ptr_named,

View File

@ -1442,11 +1442,6 @@ fn analyzeBodyInner(
i += 1;
continue;
},
.export_value => {
try sema.zirExportValue(block, inst);
i += 1;
continue;
},
.set_runtime_safety => {
try sema.zirSetRuntimeSafety(block, inst);
i += 1;
@ -6279,73 +6274,72 @@ fn zirExport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void
const ip = &zcu.intern_pool;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.Export, inst_data.payload_index).data;
const src = block.nodeOffset(inst_data.src_node);
const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const ptr_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const options_src = block.builtinCallArgSrc(inst_data.src_node, 1);
const decl_name = try ip.getOrPutString(
zcu.gpa,
pt.tid,
sema.code.nullTerminatedString(extra.decl_name),
.no_embedded_nulls,
);
const nav_index = if (extra.namespace != .none) index_blk: {
const container_ty = try sema.resolveType(block, operand_src, extra.namespace);
const container_namespace = container_ty.getNamespaceIndex(zcu);
const lookup = try sema.lookupInNamespace(block, operand_src, container_namespace, decl_name, false) orelse
return sema.failWithBadMemberAccess(block, container_ty, operand_src, decl_name);
break :index_blk lookup.nav;
} else try sema.lookupIdentifier(block, operand_src, decl_name);
const options = try sema.resolveExportOptions(block, options_src, extra.options);
try sema.ensureNavResolved(src, nav_index);
// Make sure to export the owner Nav if applicable.
const exported_nav = switch (ip.indexToKey(ip.getNav(nav_index).status.resolved.val)) {
.variable => |v| v.owner_nav,
.@"extern" => |e| e.owner_nav,
.func => |f| f.owner_nav,
else => nav_index,
};
try sema.analyzeExport(block, src, options, exported_nav);
}
fn zirExportValue(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.ExportValue, inst_data.payload_index).data;
const src = block.nodeOffset(inst_data.src_node);
const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const options_src = block.builtinCallArgSrc(inst_data.src_node, 1);
const operand = try sema.resolveInstConst(block, operand_src, extra.operand, .{
const ptr = try sema.resolveInst(extra.exported);
const ptr_val = try sema.resolveConstDefinedValue(block, ptr_src, ptr, .{
.needed_comptime_reason = "export target must be comptime-known",
});
const options = try sema.resolveExportOptions(block, options_src, extra.options);
if (options.linkage == .internal)
return;
const ptr_ty = ptr_val.typeOf(zcu);
// If the value has an owner Nav, export that instead.
const maybe_owner_nav = switch (ip.indexToKey(operand.toIntern())) {
.variable => |v| v.owner_nav,
.@"extern" => |e| e.owner_nav,
.func => |f| f.owner_nav,
else => null,
};
if (maybe_owner_nav) |owner_nav| {
return sema.analyzeExport(block, src, options, owner_nav);
} else {
try sema.exports.append(zcu.gpa, .{
.opts = options,
.src = src,
.exported = .{ .uav = operand.toIntern() },
.status = .in_progress,
});
const options = try sema.resolveExportOptions(block, options_src, extra.options);
{
if (ptr_ty.zigTypeTag(zcu) != .Pointer) {
return sema.fail(block, ptr_src, "expected pointer type, found '{}'", .{ptr_ty.fmt(pt)});
}
const ptr_ty_info = ptr_ty.ptrInfo(zcu);
if (ptr_ty_info.flags.size == .Slice) {
return sema.fail(block, ptr_src, "export target cannot be slice", .{});
}
if (ptr_ty_info.packed_offset.host_size != 0) {
return sema.fail(block, ptr_src, "export target cannot be bit-pointer", .{});
}
}
const ptr_info = ip.indexToKey(ptr_val.toIntern()).ptr;
switch (ptr_info.base_addr) {
.comptime_alloc, .int, .comptime_field => return sema.fail(block, ptr_src, "export target must be a global variable or a comptime-known constant", .{}),
.eu_payload, .opt_payload, .field, .arr_elem => return sema.fail(block, ptr_src, "TODO: export pointer in middle of value", .{}),
.uav => |uav| {
if (ptr_info.byte_offset != 0) {
return sema.fail(block, ptr_src, "TODO: export pointer in middle of value", .{});
}
if (options.linkage == .internal) return;
const export_ty = Value.fromInterned(uav.val).typeOf(zcu);
if (!try sema.validateExternType(export_ty, .other)) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "unable to export type '{}'", .{export_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
try sema.explainWhyTypeIsNotExtern(msg, src, export_ty, .other);
try sema.addDeclaredHereNote(msg, export_ty);
break :msg msg;
});
}
try sema.exports.append(zcu.gpa, .{
.opts = options,
.src = src,
.exported = .{ .uav = uav.val },
.status = .in_progress,
});
},
.nav => |nav| {
if (ptr_info.byte_offset != 0) {
return sema.fail(block, ptr_src, "TODO: export pointer in middle of value", .{});
}
try sema.ensureNavResolved(src, nav);
// Make sure to export the owner Nav if applicable.
const exported_nav = switch (ip.indexToKey(ip.getNav(nav).status.resolved.val)) {
.variable => |v| v.owner_nav,
.@"extern" => |e| e.owner_nav,
.func => |f| f.owner_nav,
else => nav,
};
try sema.analyzeExport(block, src, options, exported_nav);
},
}
}

View File

@ -429,7 +429,6 @@ const Writer = struct {
.elem_val_imm => try self.writeElemValImm(stream, inst),
.@"export" => try self.writePlNodeExport(stream, inst),
.export_value => try self.writePlNodeExportValue(stream, inst),
.call => try self.writeCall(stream, inst, .direct),
.field_call => try self.writeCall(stream, inst, .field),
@ -1007,20 +1006,8 @@ const Writer = struct {
fn writePlNodeExport(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
const inst_data = self.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = self.code.extraData(Zir.Inst.Export, inst_data.payload_index).data;
const decl_name = self.code.nullTerminatedString(extra.decl_name);
try self.writeInstRef(stream, extra.namespace);
try stream.print(", {p}, ", .{std.zig.fmtId(decl_name)});
try self.writeInstRef(stream, extra.options);
try stream.writeAll(") ");
try self.writeSrcNode(stream, inst_data.src_node);
}
fn writePlNodeExportValue(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
const inst_data = self.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = self.code.extraData(Zir.Inst.ExportValue, inst_data.payload_index).data;
try self.writeInstRef(stream, extra.operand);
try self.writeInstRef(stream, extra.exported);
try stream.writeAll(", ");
try self.writeInstRef(stream, extra.options);
try stream.writeAll(") ");