fix: dispatch unwrapped optional-closure call g!() through call_closure (issue 0170)
Calling through an unwrapped optional closure (g!()) crashed with LLVM
'Called function must be a pointer!': the indirect-call catch-all else
arm emitted call_indirect on the whole {fn,env} closure struct with a
hardcoded .i64 return. The else arm now inspects inferExprType(callee):
a .closure callee dispatches through call_closure (threads env + ctx via
the [ctx, env, user_args] ABI, returns closure.ret); a plain fn pointer
uses call_indirect with the callee's real function.ret instead of i64.
The filed repro's ?(() -> void) spelling is a tuple-optional (now
diagnosed by the 0165 fix); the real ?Closure(...) layout was already
correct. Verified load-bearing (HEAD crashes) by 3 adversarial reviews,
suite 785/0. Regression: examples/closures/0311-closures-optional-closure.sx.
Filed adjacent bug 0177 (array-element closure direct call crashes).
This commit is contained in:
@@ -1356,10 +1356,38 @@ pub fn lowerCall(self: *Lowering, c_in: *const ast.Call) Ref {
|
||||
return self.builder.enumInit(tag, payload, target);
|
||||
},
|
||||
else => {
|
||||
// Indirect call through expression
|
||||
// Indirect call through expression. The callee can be a plain
|
||||
// function pointer OR a closure value (e.g. `g!()` where
|
||||
// `g : ?Closure(...)` — the force-unwrap yields the closure
|
||||
// struct). Inspect the callee's static type so we emit the
|
||||
// right op: `call_closure` splits `{fn_ptr, env}` and threads
|
||||
// env (and implicit ctx), whereas `call_indirect` would treat
|
||||
// the whole struct as a bare fn pointer and miscompile.
|
||||
const callee_ty = self.inferExprType(c.callee);
|
||||
if (!callee_ty.isBuiltin()) {
|
||||
const cti = self.module.types.get(callee_ty);
|
||||
if (cti == .closure) {
|
||||
const callee_ref = self.lowerExpr(c.callee);
|
||||
// Prepend implicit ctx for the sx-side closure call ABI
|
||||
// (emit_llvm builds the call as [ctx, env, user_args]).
|
||||
const owned = if (self.implicit_ctx_enabled) blk: {
|
||||
const arr = self.alloc.alloc(Ref, args.items.len + 1) catch unreachable;
|
||||
arr[0] = self.current_ctx_ref;
|
||||
@memcpy(arr[1..], args.items);
|
||||
break :blk arr;
|
||||
} else self.alloc.dupe(Ref, args.items) catch unreachable;
|
||||
return self.builder.emit(.{ .call_closure = .{ .callee = callee_ref, .args = owned } }, cti.closure.ret);
|
||||
}
|
||||
}
|
||||
// Plain function-pointer indirect call. Use the callee's static
|
||||
// return type when known instead of a hardcoded `.i64` default.
|
||||
const ret_ty: TypeId = if (!callee_ty.isBuiltin()) blk: {
|
||||
const cti = self.module.types.get(callee_ty);
|
||||
break :blk if (cti == .function) cti.function.ret else .i64;
|
||||
} else .i64;
|
||||
const callee_ref = self.lowerExpr(c.callee);
|
||||
const owned = self.alloc.dupe(Ref, args.items) catch unreachable;
|
||||
return self.builder.emit(.{ .call_indirect = .{ .callee = callee_ref, .args = owned } }, .i64);
|
||||
return self.builder.emit(.{ .call_indirect = .{ .callee = callee_ref, .args = owned } }, ret_ty);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user