stage2: comptime function calls
* Function calls that happen in a comptime scope get called at compile-time. We do this by putting the parameters in place as constant values and then running regular function analysis on the body. * Added `Scope.Block.dump()` for debugging purposes. * Fixed some code to call `identifierTokenString` rather than `tokenSlice`, making it work for `@""` syntax. * Implemented `Value.copy` for big integers. Follow-up issues to tackle: * Adding compile errors to the callsite instead of the callee Decl. * Proper error notes for "called from here". - Related: #7555 * Branch quotas. * ZIR support?
This commit is contained in:
@@ -268,6 +268,11 @@ pub const Decl = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// Asserts that the `Decl` is part of AST and not ZIRModule.
|
||||
pub fn getFileScope(self: *Decl) *Scope.File {
|
||||
return self.scope.cast(Scope.Container).?.file_scope;
|
||||
}
|
||||
|
||||
fn removeDependant(self: *Decl, other: *Decl) void {
|
||||
self.dependants.removeAssertDiscard(other);
|
||||
}
|
||||
@@ -776,6 +781,11 @@ pub const Scope = struct {
|
||||
results: ArrayListUnmanaged(*Inst),
|
||||
block_inst: *Inst.Block,
|
||||
};
|
||||
|
||||
/// For debugging purposes.
|
||||
pub fn dump(self: *Block, mod: Module) void {
|
||||
zir.dumpBlock(mod, self);
|
||||
}
|
||||
};
|
||||
|
||||
/// This is a temporary structure, references to it are valid only
|
||||
@@ -992,11 +1002,11 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
|
||||
defer tracy.end();
|
||||
|
||||
const container_scope = decl.scope.cast(Scope.Container).?;
|
||||
const tree = try self.getAstTree(container_scope);
|
||||
const tree = try self.getAstTree(container_scope.file_scope);
|
||||
const ast_node = tree.root_node.decls()[decl.src_index];
|
||||
switch (ast_node.tag) {
|
||||
.FnProto => {
|
||||
const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", ast_node);
|
||||
const fn_proto = ast_node.castTag(.FnProto).?;
|
||||
|
||||
decl.analysis = .in_progress;
|
||||
|
||||
@@ -1131,7 +1141,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
|
||||
for (fn_proto.params()) |param, i| {
|
||||
const name_token = param.name_token.?;
|
||||
const src = tree.token_locs[name_token].start;
|
||||
const param_name = tree.tokenSlice(name_token); // TODO: call identifierTokenString
|
||||
const param_name = try self.identifierTokenString(&gen_scope.base, name_token);
|
||||
const arg = try gen_scope_arena.allocator.create(zir.Inst.Arg);
|
||||
arg.* = .{
|
||||
.base = .{
|
||||
@@ -1496,12 +1506,10 @@ fn getSrcModule(self: *Module, root_scope: *Scope.ZIRModule) !*zir.Module {
|
||||
}
|
||||
}
|
||||
|
||||
fn getAstTree(self: *Module, container_scope: *Scope.Container) !*ast.Tree {
|
||||
pub fn getAstTree(self: *Module, root_scope: *Scope.File) !*ast.Tree {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
const root_scope = container_scope.file_scope;
|
||||
|
||||
switch (root_scope.status) {
|
||||
.never_loaded, .unloaded_success => {
|
||||
try self.failed_files.ensureCapacity(self.gpa, self.failed_files.items().len + 1);
|
||||
@@ -1549,7 +1557,7 @@ pub fn analyzeContainer(self: *Module, container_scope: *Scope.Container) !void
|
||||
|
||||
// We may be analyzing it for the first time, or this may be
|
||||
// an incremental update. This code handles both cases.
|
||||
const tree = try self.getAstTree(container_scope);
|
||||
const tree = try self.getAstTree(container_scope.file_scope);
|
||||
const decls = tree.root_node.decls();
|
||||
|
||||
try self.comp.work_queue.ensureUnusedCapacity(decls.len);
|
||||
@@ -3427,3 +3435,23 @@ pub fn validateVarType(mod: *Module, scope: *Scope, src: usize, ty: Type) !void
|
||||
return mod.fail(scope, src, "variable of type '{}' must be const or comptime", .{ty});
|
||||
}
|
||||
}
|
||||
|
||||
/// Identifier token -> String (allocated in scope.arena())
|
||||
pub fn identifierTokenString(mod: *Module, scope: *Scope, token: ast.TokenIndex) InnerError![]const u8 {
|
||||
const tree = scope.tree();
|
||||
|
||||
const ident_name = tree.tokenSlice(token);
|
||||
if (mem.startsWith(u8, ident_name, "@")) {
|
||||
const raw_string = ident_name[1..];
|
||||
var bad_index: usize = undefined;
|
||||
return std.zig.parseStringLiteral(scope.arena(), raw_string, &bad_index) catch |err| switch (err) {
|
||||
error.InvalidCharacter => {
|
||||
const bad_byte = raw_string[bad_index];
|
||||
const src = tree.token_locs[token].start;
|
||||
return mod.fail(scope, src + 1 + bad_index, "invalid string literal character: '{c}'\n", .{bad_byte});
|
||||
},
|
||||
else => |e| return e,
|
||||
};
|
||||
}
|
||||
return ident_name;
|
||||
}
|
||||
|
||||
@@ -384,7 +384,7 @@ fn breakExpr(mod: *Module, parent_scope: *Scope, node: *ast.Node.ControlFlowExpr
|
||||
.local_val => scope = scope.cast(Scope.LocalVal).?.parent,
|
||||
.local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
|
||||
else => if (node.getLabel()) |break_label| {
|
||||
const label_name = try identifierTokenString(mod, parent_scope, break_label);
|
||||
const label_name = try mod.identifierTokenString(parent_scope, break_label);
|
||||
return mod.failTok(parent_scope, break_label, "label not found: '{s}'", .{label_name});
|
||||
} else {
|
||||
return mod.failTok(parent_scope, src, "break expression outside loop", .{});
|
||||
@@ -426,7 +426,7 @@ fn continueExpr(mod: *Module, parent_scope: *Scope, node: *ast.Node.ControlFlowE
|
||||
.local_val => scope = scope.cast(Scope.LocalVal).?.parent,
|
||||
.local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
|
||||
else => if (node.getLabel()) |break_label| {
|
||||
const label_name = try identifierTokenString(mod, parent_scope, break_label);
|
||||
const label_name = try mod.identifierTokenString(parent_scope, break_label);
|
||||
return mod.failTok(parent_scope, break_label, "label not found: '{s}'", .{label_name});
|
||||
} else {
|
||||
return mod.failTok(parent_scope, src, "continue expression outside loop", .{});
|
||||
@@ -551,7 +551,7 @@ fn varDecl(
|
||||
}
|
||||
const tree = scope.tree();
|
||||
const name_src = tree.token_locs[node.name_token].start;
|
||||
const ident_name = try identifierTokenString(mod, scope, node.name_token);
|
||||
const ident_name = try mod.identifierTokenString(scope, node.name_token);
|
||||
|
||||
// Local variables shadowing detection, including function parameters.
|
||||
{
|
||||
@@ -843,7 +843,7 @@ fn typeInixOp(mod: *Module, scope: *Scope, node: *ast.Node.SimpleInfixOp, op_ins
|
||||
fn enumLiteral(mod: *Module, scope: *Scope, node: *ast.Node.EnumLiteral) !*zir.Inst {
|
||||
const tree = scope.tree();
|
||||
const src = tree.token_locs[node.name].start;
|
||||
const name = try identifierTokenString(mod, scope, node.name);
|
||||
const name = try mod.identifierTokenString(scope, node.name);
|
||||
|
||||
return addZIRInst(mod, scope, src, zir.Inst.EnumLiteral, .{ .name = name }, .{});
|
||||
}
|
||||
@@ -864,7 +864,7 @@ fn errorSetDecl(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Erro
|
||||
|
||||
for (decls) |decl, i| {
|
||||
const tag = decl.castTag(.ErrorTag).?;
|
||||
fields[i] = try identifierTokenString(mod, scope, tag.name_token);
|
||||
fields[i] = try mod.identifierTokenString(scope, tag.name_token);
|
||||
}
|
||||
|
||||
// analyzing the error set results in a decl ref, so we might need to dereference it
|
||||
@@ -988,36 +988,16 @@ fn orelseCatchExpr(
|
||||
/// Return whether the identifier names of two tokens are equal. Resolves @"" tokens without allocating.
|
||||
/// OK in theory it could do it without allocating. This implementation allocates when the @"" form is used.
|
||||
fn tokenIdentEql(mod: *Module, scope: *Scope, token1: ast.TokenIndex, token2: ast.TokenIndex) !bool {
|
||||
const ident_name_1 = try identifierTokenString(mod, scope, token1);
|
||||
const ident_name_2 = try identifierTokenString(mod, scope, token2);
|
||||
const ident_name_1 = try mod.identifierTokenString(scope, token1);
|
||||
const ident_name_2 = try mod.identifierTokenString(scope, token2);
|
||||
return mem.eql(u8, ident_name_1, ident_name_2);
|
||||
}
|
||||
|
||||
/// Identifier token -> String (allocated in scope.arena())
|
||||
fn identifierTokenString(mod: *Module, scope: *Scope, token: ast.TokenIndex) InnerError![]const u8 {
|
||||
const tree = scope.tree();
|
||||
|
||||
const ident_name = tree.tokenSlice(token);
|
||||
if (mem.startsWith(u8, ident_name, "@")) {
|
||||
const raw_string = ident_name[1..];
|
||||
var bad_index: usize = undefined;
|
||||
return std.zig.parseStringLiteral(scope.arena(), raw_string, &bad_index) catch |err| switch (err) {
|
||||
error.InvalidCharacter => {
|
||||
const bad_byte = raw_string[bad_index];
|
||||
const src = tree.token_locs[token].start;
|
||||
return mod.fail(scope, src + 1 + bad_index, "invalid string literal character: '{c}'\n", .{bad_byte});
|
||||
},
|
||||
else => |e| return e,
|
||||
};
|
||||
}
|
||||
return ident_name;
|
||||
}
|
||||
|
||||
pub fn identifierStringInst(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerError!*zir.Inst {
|
||||
const tree = scope.tree();
|
||||
const src = tree.token_locs[node.token].start;
|
||||
|
||||
const ident_name = try identifierTokenString(mod, scope, node.token);
|
||||
const ident_name = try mod.identifierTokenString(scope, node.token);
|
||||
|
||||
return addZIRInst(mod, scope, src, zir.Inst.Str, .{ .bytes = ident_name }, .{});
|
||||
}
|
||||
@@ -1936,7 +1916,7 @@ fn identifier(mod: *Module, scope: *Scope, rl: ResultLoc, ident: *ast.Node.OneTo
|
||||
defer tracy.end();
|
||||
|
||||
const tree = scope.tree();
|
||||
const ident_name = try identifierTokenString(mod, scope, ident.token);
|
||||
const ident_name = try mod.identifierTokenString(scope, ident.token);
|
||||
const src = tree.token_locs[ident.token].start;
|
||||
if (mem.eql(u8, ident_name, "_")) {
|
||||
return mod.failNode(scope, &ident.base, "TODO implement '_' identifier", .{});
|
||||
|
||||
@@ -330,11 +330,14 @@ pub const Value = extern union {
|
||||
.int_type => return self.copyPayloadShallow(allocator, Payload.IntType),
|
||||
.int_u64 => return self.copyPayloadShallow(allocator, Payload.U64),
|
||||
.int_i64 => return self.copyPayloadShallow(allocator, Payload.I64),
|
||||
.int_big_positive => {
|
||||
@panic("TODO implement copying of big ints");
|
||||
},
|
||||
.int_big_negative => {
|
||||
@panic("TODO implement copying of big ints");
|
||||
.int_big_positive, .int_big_negative => {
|
||||
const old_payload = self.cast(Payload.BigInt).?;
|
||||
const new_payload = try allocator.create(Payload.BigInt);
|
||||
new_payload.* = .{
|
||||
.base = .{ .tag = self.ptr_otherwise.tag },
|
||||
.data = try allocator.dupe(std.math.big.Limb, old_payload.data),
|
||||
};
|
||||
return Value{ .ptr_otherwise = &new_payload.base };
|
||||
},
|
||||
.function => return self.copyPayloadShallow(allocator, Payload.Function),
|
||||
.extern_fn => return self.copyPayloadShallow(allocator, Payload.Decl),
|
||||
|
||||
70
src/zir.zig
70
src/zir.zig
@@ -1885,6 +1885,46 @@ pub fn dumpFn(old_module: IrModule, module_fn: *IrModule.Fn) void {
|
||||
module.dump();
|
||||
}
|
||||
|
||||
/// For debugging purposes, prints a function representation to stderr.
|
||||
pub fn dumpBlock(old_module: IrModule, module_block: *IrModule.Scope.Block) void {
|
||||
const allocator = old_module.gpa;
|
||||
var ctx: EmitZIR = .{
|
||||
.allocator = allocator,
|
||||
.decls = .{},
|
||||
.arena = std.heap.ArenaAllocator.init(allocator),
|
||||
.old_module = &old_module,
|
||||
.next_auto_name = 0,
|
||||
.names = std.StringArrayHashMap(void).init(allocator),
|
||||
.primitive_table = std.AutoHashMap(Inst.Primitive.Builtin, *Decl).init(allocator),
|
||||
.indent = 0,
|
||||
.block_table = std.AutoHashMap(*ir.Inst.Block, *Inst.Block).init(allocator),
|
||||
.loop_table = std.AutoHashMap(*ir.Inst.Loop, *Inst.Loop).init(allocator),
|
||||
.metadata = std.AutoHashMap(*Inst, Module.MetaData).init(allocator),
|
||||
.body_metadata = std.AutoHashMap(*Module.Body, Module.BodyMetaData).init(allocator),
|
||||
};
|
||||
defer ctx.metadata.deinit();
|
||||
defer ctx.body_metadata.deinit();
|
||||
defer ctx.block_table.deinit();
|
||||
defer ctx.loop_table.deinit();
|
||||
defer ctx.decls.deinit(allocator);
|
||||
defer ctx.names.deinit();
|
||||
defer ctx.primitive_table.deinit();
|
||||
defer ctx.arena.deinit();
|
||||
|
||||
_ = ctx.emitBlock(module_block, 0) catch |err| {
|
||||
std.debug.print("unable to dump function: {}\n", .{err});
|
||||
return;
|
||||
};
|
||||
var module = Module{
|
||||
.decls = ctx.decls.items,
|
||||
.arena = ctx.arena,
|
||||
.metadata = ctx.metadata,
|
||||
.body_metadata = ctx.body_metadata,
|
||||
};
|
||||
|
||||
module.dump();
|
||||
}
|
||||
|
||||
const EmitZIR = struct {
|
||||
allocator: *Allocator,
|
||||
arena: std.heap.ArenaAllocator,
|
||||
@@ -2065,6 +2105,36 @@ const EmitZIR = struct {
|
||||
return &declref_inst.base;
|
||||
}
|
||||
|
||||
fn emitBlock(self: *EmitZIR, module_block: *IrModule.Scope.Block, src: usize) Allocator.Error!*Decl {
|
||||
var inst_table = std.AutoHashMap(*ir.Inst, *Inst).init(self.allocator);
|
||||
defer inst_table.deinit();
|
||||
|
||||
var instructions = std.ArrayList(*Inst).init(self.allocator);
|
||||
defer instructions.deinit();
|
||||
|
||||
const body: ir.Body = .{ .instructions = module_block.instructions.items };
|
||||
try self.emitBody(body, &inst_table, &instructions);
|
||||
|
||||
const fn_type = try self.emitType(src, Type.initTag(.void));
|
||||
|
||||
const arena_instrs = try self.arena.allocator.alloc(*Inst, instructions.items.len);
|
||||
mem.copy(*Inst, arena_instrs, instructions.items);
|
||||
|
||||
const fn_inst = try self.arena.allocator.create(Inst.Fn);
|
||||
fn_inst.* = .{
|
||||
.base = .{
|
||||
.src = src,
|
||||
.tag = Inst.Fn.base_tag,
|
||||
},
|
||||
.positionals = .{
|
||||
.fn_type = fn_type.inst,
|
||||
.body = .{ .instructions = arena_instrs },
|
||||
},
|
||||
.kw_args = .{},
|
||||
};
|
||||
return self.emitUnnamedDecl(&fn_inst.base);
|
||||
}
|
||||
|
||||
fn emitFn(self: *EmitZIR, module_fn: *IrModule.Fn, src: usize, ty: Type) Allocator.Error!*Decl {
|
||||
var inst_table = std.AutoHashMap(*ir.Inst, *Inst).init(self.allocator);
|
||||
defer inst_table.deinit();
|
||||
|
||||
115
src/zir_sema.zig
115
src/zir_sema.zig
@@ -25,6 +25,8 @@ const trace = @import("tracy.zig").trace;
|
||||
const Scope = Module.Scope;
|
||||
const InnerError = Module.InnerError;
|
||||
const Decl = Module.Decl;
|
||||
const astgen = @import("astgen.zig");
|
||||
const ast = std.zig.ast;
|
||||
|
||||
pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst {
|
||||
switch (old_inst.tag) {
|
||||
@@ -826,7 +828,112 @@ fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError
|
||||
|
||||
const ret_type = func.ty.fnReturnType();
|
||||
|
||||
const b = try mod.requireRuntimeBlock(scope, inst.base.src);
|
||||
const b = try mod.requireFunctionBlock(scope, inst.base.src);
|
||||
if (b.is_comptime) {
|
||||
const fn_val = try mod.resolveConstValue(scope, func);
|
||||
const module_fn = switch (fn_val.tag()) {
|
||||
.function => fn_val.castTag(.function).?.data,
|
||||
.extern_fn => return mod.fail(scope, inst.base.src, "comptime call of extern function", .{}),
|
||||
else => unreachable,
|
||||
};
|
||||
const callee_decl = module_fn.owner_decl;
|
||||
const callee_file_scope = callee_decl.getFileScope();
|
||||
const tree = mod.getAstTree(callee_file_scope) catch |err| switch (err) {
|
||||
error.OutOfMemory => return error.OutOfMemory,
|
||||
error.AnalysisFail => return error.AnalysisFail,
|
||||
// TODO: make sure this gets retried and not cached
|
||||
else => return mod.fail(scope, inst.base.src, "failed to load {s}: {s}", .{
|
||||
callee_file_scope.sub_file_path, @errorName(err),
|
||||
}),
|
||||
};
|
||||
const ast_node = tree.root_node.decls()[callee_decl.src_index];
|
||||
const fn_proto = ast_node.castTag(.FnProto).?;
|
||||
|
||||
var call_arena = std.heap.ArenaAllocator.init(mod.gpa);
|
||||
defer call_arena.deinit();
|
||||
|
||||
var gen_scope: Scope.GenZIR = .{
|
||||
.decl = callee_decl,
|
||||
.arena = &call_arena.allocator,
|
||||
.parent = callee_decl.scope,
|
||||
};
|
||||
defer gen_scope.instructions.deinit(mod.gpa);
|
||||
|
||||
// Add a const instruction for each parameter.
|
||||
var params_scope = &gen_scope.base;
|
||||
for (fn_proto.params()) |param, i| {
|
||||
const name_token = param.name_token.?;
|
||||
const src = tree.token_locs[name_token].start;
|
||||
const param_name = try mod.identifierTokenString(scope, name_token);
|
||||
const arg_val = try mod.resolveConstValue(scope, casted_args[i]);
|
||||
const arg = try astgen.addZIRInstConst(mod, params_scope, src, .{
|
||||
.ty = casted_args[i].ty,
|
||||
.val = arg_val,
|
||||
});
|
||||
const sub_scope = try call_arena.allocator.create(Scope.LocalVal);
|
||||
sub_scope.* = .{
|
||||
.parent = params_scope,
|
||||
.gen_zir = &gen_scope,
|
||||
.name = param_name,
|
||||
.inst = arg,
|
||||
};
|
||||
params_scope = &sub_scope.base;
|
||||
}
|
||||
|
||||
const body_node = fn_proto.getBodyNode().?; // We handle extern functions above.
|
||||
const body_block = body_node.cast(ast.Node.Block).?;
|
||||
|
||||
try astgen.blockExpr(mod, params_scope, body_block);
|
||||
|
||||
if (gen_scope.instructions.items.len == 0 or
|
||||
!gen_scope.instructions.items[gen_scope.instructions.items.len - 1].tag.isNoReturn())
|
||||
{
|
||||
const src = tree.token_locs[body_block.rbrace].start;
|
||||
_ = try astgen.addZIRNoOp(mod, &gen_scope.base, src, .returnvoid);
|
||||
}
|
||||
|
||||
if (mod.comp.verbose_ir) {
|
||||
zir.dumpZir(mod.gpa, "fn_body_callee", callee_decl.name, gen_scope.instructions.items) catch {};
|
||||
}
|
||||
|
||||
// Analyze the ZIR.
|
||||
var inner_block: Scope.Block = .{
|
||||
.parent = null,
|
||||
.func = module_fn,
|
||||
.decl = callee_decl,
|
||||
.instructions = .{},
|
||||
.arena = &call_arena.allocator,
|
||||
.is_comptime = true,
|
||||
};
|
||||
defer inner_block.instructions.deinit(mod.gpa);
|
||||
|
||||
// TODO make sure compile errors that happen from this analyzeBody are reported correctly
|
||||
// and attach to the caller Decl not the callee.
|
||||
try analyzeBody(mod, &inner_block.base, .{
|
||||
.instructions = gen_scope.instructions.items,
|
||||
});
|
||||
|
||||
if (mod.comp.verbose_ir) {
|
||||
inner_block.dump(mod.*);
|
||||
}
|
||||
|
||||
assert(inner_block.instructions.items.len == 1);
|
||||
const only_inst = inner_block.instructions.items[0];
|
||||
switch (only_inst.tag) {
|
||||
.ret => {
|
||||
const ret_inst = only_inst.castTag(.ret).?;
|
||||
const operand = ret_inst.operand;
|
||||
const callee_arena = scope.arena();
|
||||
return mod.constInst(scope, inst.base.src, .{
|
||||
.ty = try operand.ty.copy(callee_arena),
|
||||
.val = try operand.value().?.copy(callee_arena),
|
||||
});
|
||||
},
|
||||
.retvoid => return mod.constVoid(scope, inst.base.src),
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
return mod.addCall(b, inst.base.src, ret_type, func, casted_args);
|
||||
}
|
||||
|
||||
@@ -1509,7 +1616,7 @@ fn analyzeInstImport(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerErr
|
||||
return mod.fail(scope, inst.base.src, "unable to find '{s}'", .{operand});
|
||||
},
|
||||
else => {
|
||||
// TODO user friendly error to string
|
||||
// TODO: make sure this gets retried and not cached
|
||||
return mod.fail(scope, inst.base.src, "unable to open '{s}': {s}", .{ operand, @errorName(err) });
|
||||
},
|
||||
};
|
||||
@@ -1912,12 +2019,12 @@ fn analyzeInstUnreachable(
|
||||
|
||||
fn analyzeInstRet(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
|
||||
const operand = try resolveInst(mod, scope, inst.positionals.operand);
|
||||
const b = try mod.requireRuntimeBlock(scope, inst.base.src);
|
||||
const b = try mod.requireFunctionBlock(scope, inst.base.src);
|
||||
return mod.addUnOp(b, inst.base.src, Type.initTag(.noreturn), .ret, operand);
|
||||
}
|
||||
|
||||
fn analyzeInstRetVoid(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {
|
||||
const b = try mod.requireRuntimeBlock(scope, inst.base.src);
|
||||
const b = try mod.requireFunctionBlock(scope, inst.base.src);
|
||||
if (b.func) |func| {
|
||||
// Need to emit a compile error if returning void is not allowed.
|
||||
const void_inst = try mod.constVoid(scope, inst.base.src);
|
||||
|
||||
@@ -318,7 +318,7 @@ pub fn addCases(ctx: *TestContext) !void {
|
||||
}
|
||||
|
||||
{
|
||||
var case = ctx.exe("adding numbers at runtime", linux_x64);
|
||||
var case = ctx.exe("adding numbers at runtime and comptime", linux_x64);
|
||||
case.addCompareOutput(
|
||||
\\export fn _start() noreturn {
|
||||
\\ add(3, 4);
|
||||
@@ -342,6 +342,29 @@ pub fn addCases(ctx: *TestContext) !void {
|
||||
,
|
||||
"",
|
||||
);
|
||||
case.addCompareOutput(
|
||||
\\export fn _start() noreturn {
|
||||
\\ exit();
|
||||
\\}
|
||||
\\
|
||||
\\fn add(a: u32, b: u32) u32 {
|
||||
\\ return a + b;
|
||||
\\}
|
||||
\\
|
||||
\\const x = add(3, 4);
|
||||
\\
|
||||
\\fn exit() noreturn {
|
||||
\\ asm volatile ("syscall"
|
||||
\\ :
|
||||
\\ : [number] "{rax}" (231),
|
||||
\\ [arg1] "{rdi}" (x - 7)
|
||||
\\ : "rcx", "r11", "memory"
|
||||
\\ );
|
||||
\\ unreachable;
|
||||
\\}
|
||||
,
|
||||
"",
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user