diff --git a/doc/langref.html.in b/doc/langref.html.in
index 9cd1844e5118..df63eaaecc1d 100644
--- a/doc/langref.html.in
+++ b/doc/langref.html.in
@@ -8228,7 +8228,7 @@ test "main" {
{#syntax#}@divExact(a, b) * b == a{#endsyntax#}
For a function that returns a possible error code, use {#syntax#}@import("std").math.divExact{#endsyntax#}.
- {#see_also|@divTrunc|@divFloor#}
+ {#see_also|@divTrunc|@divFloor|@divCeil#}
{#header_close#}
{#header_open|@divFloor#}
{#syntax#}@divFloor(numerator: T, denominator: T) T{#endsyntax#}
@@ -8242,7 +8242,20 @@ test "main" {
{#syntax#}(@divFloor(a, b) * b) + @mod(a, b) == a{#endsyntax#}
For a function that returns a possible error code, use {#syntax#}@import("std").math.divFloor{#endsyntax#}.
- {#see_also|@divTrunc|@divExact#}
+ {#see_also|@divTrunc|@divExact|@divCeil#}
+ {#header_close#}
+ {#header_open|@divCeil#}
+ {#syntax#}@divCeil(numerator: T, denominator: T) T{#endsyntax#}
+
+ Ceiling division. Rounds toward positive infinity. Caller guarantees {#syntax#}denominator != 0{#endsyntax#} and
+ {#syntax#}!(@typeInfo(T) == .Int and T.is_signed and numerator == std.math.minInt(T) and denominator == -1){#endsyntax#}.
+
+
+ - {#syntax#}@divCeil(5, 3) == 2{#endsyntax#}
+ - {#syntax#}@divCeil(-5, 3) == -1{#endsyntax#}
+
+ For a function that returns a possible error code, use {#syntax#}@import("std").math.divCeil{#endsyntax#}.
+ {#see_also|@divTrunc|@divExact|@divFloor#}
{#header_close#}
{#header_open|@divTrunc#}
{#syntax#}@divTrunc(numerator: T, denominator: T) T{#endsyntax#}
@@ -8256,7 +8269,7 @@ test "main" {
{#syntax#}(@divTrunc(a, b) * b) + @rem(a, b) == a{#endsyntax#}
For a function that returns a possible error code, use {#syntax#}@import("std").math.divTrunc{#endsyntax#}.
- {#see_also|@divFloor|@divExact#}
+ {#see_also|@divFloor|@divCeil|@divExact#}
{#header_close#}
{#header_open|@embedFile#}
diff --git a/lib/docs/main.js b/lib/docs/main.js
index 4991c31b57f9..548257d002ca 100644
--- a/lib/docs/main.js
+++ b/lib/docs/main.js
@@ -1,16 +1,16 @@
"use strict";
var zigAnalysis = {
- typeKinds,
- rootMod,
- modules,
- astNodes,
- calls,
- files,
- decls,
- exprs,
+ typeKinds,
+ rootMod,
+ modules,
+ astNodes,
+ calls,
+ files,
+ decls,
+ exprs,
types,
- comptimeExprs,
+ comptimeExprs,
guideSections
};
@@ -459,18 +459,18 @@ var scrollHistory = {};
let separator = ":";
const components = text.split(".");
let curDeclOrType = undefined;
-
+
let curContext = context;
let limit = 10000;
while (curContext) {
limit -= 1;
-
+
if (limit == 0) {
throw "too many iterations";
}
-
+
curDeclOrType = findSubDecl(curContext, components[0]);
-
+
if (!curDeclOrType) {
if (curContext.parent_container == null) break;
curContext = getType(curContext.parent_container);
@@ -484,16 +484,16 @@ var scrollHistory = {};
// We had to go up, which means we need a new path!
const canonPath = getCanonDeclPath(curDeclOrType.find_subdecl_idx);
if (!canonPath) return;
-
+
let lastModName = canonPath.modNames[canonPath.modNames.length - 1];
let fullPath = lastModName + ":" + canonPath.declNames.join(".");
-
+
separator = '.';
result = "#A;" + fullPath;
}
break;
- }
+ }
if (!curDeclOrType) {
for (let i = 0; i < zigAnalysis.modules.length; i += 1){
@@ -507,7 +507,7 @@ var scrollHistory = {};
}
if (!curDeclOrType) return null;
-
+
for (let i = 1; i < components.length; i += 1) {
curDeclOrType = findSubDecl(curDeclOrType, components[i]);
if (!curDeclOrType) return null;
@@ -516,9 +516,9 @@ var scrollHistory = {};
}
return result;
-
+
}
-
+
function renderGuides() {
renderTitle();
@@ -550,8 +550,8 @@ var scrollHistory = {};
}
- // navigation bar
-
+ // navigation bar
+
const guideIndexDom = domListNavGuides.children[0].children[0];
const guideDom = domListNavGuides.children[1].children[0];
if (activeGuide){
@@ -562,7 +562,7 @@ var scrollHistory = {};
} else {
guideDom.classList.add("hidden");
guideIndexDom.classList.add("active");
- }
+ }
// main content
domGuidesMenuTitle.textContent = "Table of Contents";
@@ -579,7 +579,7 @@ var scrollHistory = {};
ev.stopPropagation();
}
for (let a of domGuideTocList.querySelectorAll("a")) {
- a.addEventListener('click', onLinkClick, false);
+ a.addEventListener('click', onLinkClick, false);
}
domGuideTocList.classList.remove("hidden");
domGuideTocListEmtpy.classList.add("hidden");
@@ -587,16 +587,16 @@ var scrollHistory = {};
domGuideTocListEmtpy.classList.remove("hidden");
domGuideTocList.classList.add("hidden");
}
-
+
let reader = new commonmark.Parser({
smart: true,
autoDoc: {
detectDeclPath: detectDeclPath,
}
});
- let ast = reader.parse(activeGuide.body);
- let writer = new commonmark.HtmlRenderer();
- let result = writer.render(ast);
+ let ast = reader.parse(activeGuide.body);
+ let writer = new commonmark.HtmlRenderer();
+ let result = writer.render(ast);
domActiveGuide.innerHTML = result;
if (curNav.activeGuideScrollTo !== null) {
scrollToHeading(curNav.activeGuideScrollTo, false);
@@ -604,7 +604,7 @@ var scrollHistory = {};
} else {
domGuideTocList.classList.add("hidden");
domGuideTocListEmtpy.classList.remove("hidden");
-
+
if (zigAnalysis.guideSections.length > 1 || (zigAnalysis.guideSections[0].guides.length > 0)) {
renderGuidesIndex();
} else {
@@ -626,7 +626,7 @@ var scrollHistory = {};
}
function renderGuidesIndex() {
- // main content
+ // main content
{
let html = "";
for (let i = 0; i < zigAnalysis.guideSections.length; i += 1) {
@@ -662,16 +662,16 @@ var scrollHistory = {};
ev.stopPropagation();
}
for (let a of domGuideTocList.querySelectorAll("a")) {
- a.addEventListener('click', onLinkClick, false);
+ a.addEventListener('click', onLinkClick, false);
}
-
+
domGuideTocList.classList.remove("hidden");
domGuideTocListEmtpy.classList.add("hidden");
} else {
domGuideTocList.classList.add("hidden");
domGuideTocListEmtpy.classList.remove("hidden");
}
- }
+ }
}
function noGuidesAtAll() {
@@ -685,8 +685,8 @@ These autodocs don't contain any guide.
While the API section is a reference guide autogenerated from Zig source code,
guides are meant to be handwritten explanations that provide for example:
-- how-to explanations for common use-cases
-- technical documentation
+- how-to explanations for common use-cases
+- technical documentation
- information about advanced usage patterns
You can add guides by specifying which markdown files to include
@@ -706,7 +706,7 @@ You can also create sections to group guides together:
//!zig-autodoc-guide: cli-basics.md
//!zig-autodoc-guide: cli-advanced.md
\`\`\`
-
+
**Note that this feature is still under heavy development so expect bugs**
**and missing features!**
@@ -714,8 +714,8 @@ You can also create sections to group guides together:
Happy writing!
`);
- let writer = new commonmark.HtmlRenderer();
- let result = writer.render(ast);
+ let writer = new commonmark.HtmlRenderer();
+ let result = writer.render(ast);
domActiveGuide.innerHTML = result;
}
@@ -1417,7 +1417,7 @@ Happy writing!
}
yield { src: ")", tag: Tag.r_paren };
return;
- }
+ }
case "sizeOf": {
const sizeOf = zigAnalysis.exprs[expr.sizeOf];
yield { src: "@sizeOf", tag: Tag.builtin };
@@ -1506,7 +1506,7 @@ Happy writing!
yield Tok.r_bracket;
return;
}
-
+
case "sliceIndex": {
const slice = zigAnalysis.exprs[expr.sliceIndex];
yield* ex(slice, opts);
@@ -1631,7 +1631,7 @@ Happy writing!
}
return;
}
-
+
case "fieldVal": {
const fv = expr.fieldVal;
const field_name = fv.name;
@@ -1904,6 +1904,10 @@ Happy writing!
builtinName += "divFloor";
break;
}
+ case "div_ceil": {
+ builtinName += "divCeil";
+ break;
+ }
case "div_trunc": {
builtinName += "divTrunc";
break;
@@ -2394,9 +2398,9 @@ Happy writing!
}
if (enumObj.nonexhaustive) {
for (let j = 0; j < indent; j += 1) yield Tok.tab;
-
+
yield { src: "_", tag: Tag.identifier };
-
+
if (fields_len > 1) {
yield Tok.comma;
yield Tok.enter;
@@ -2877,7 +2881,7 @@ Happy writing!
resolvedValue.expr.call !== undefined ||
resolvedValue.expr.comptimeExpr !== undefined
) {
- // TODO: we're using the resolved value but
+ // TODO: we're using the resolved value but
// not keeping track of how we got there
// that's important context that should
// be shown to the user!
@@ -2931,7 +2935,7 @@ Happy writing!
let docs = getAstNode(decl.src).docs;
if (docs != null) {
- // TODO: it shouldn't just be decl.parent_container, but rather
+ // TODO: it shouldn't just be decl.parent_container, but rather
// the type that the decl holds (if the value is a type)
domTldDocs.innerHTML = markdown(docs, decl);
@@ -3061,11 +3065,11 @@ Happy writing!
if (typeIsErrSet(declValue.expr.type)) {
errSetsList.push(decl);
} else if (typeIsStructWithNoFields(declValue.expr.type)) {
-
+
let docs = getAstNode(decl.src).docs;
if (!docs) {
// If this is a re-export, try to fetch docs from the actual definition
- const { value, seenDecls } = resolveValue(decl.value, true);
+ const { value, seenDecls } = resolveValue(decl.value, true);
if (seenDecls.length > 0) {
const definitionDecl = getDecl(seenDecls[seenDecls.length - 1]);
docs = getAstNode(definitionDecl.src).docs;
@@ -3073,7 +3077,7 @@ Happy writing!
docs = getAstNode(getType(value.expr.type).src).docs;
}
}
-
+
if (docs) {
namespacesWithDocsList.push({decl, docs});
} else {
@@ -3092,7 +3096,7 @@ Happy writing!
let docs = getAstNode(decl.src).docs;
if (!docs) {
// If this is a re-export, try to fetch docs from the actual definition
- const { value, seenDecls } = resolveValue(decl.value, true);
+ const { value, seenDecls } = resolveValue(decl.value, true);
if (seenDecls.length > 0) {
const definitionDecl = getDecl(seenDecls[seenDecls.length - 1]);
docs = getAstNode(definitionDecl.src).docs;
@@ -3244,12 +3248,12 @@ Happy writing!
let decl = typesList[i];
aDom.textContent = decl.name;
aDom.setAttribute("href", navLinkDecl(decl.name));
-
+
let descDom = liDom.children[1];
let docs = getAstNode(decl.src).docs;
if (!docs) {
// If this is a re-export, try to fetch docs from the actual definition
- const { value, seenDecls } = resolveValue(decl.value, true);
+ const { value, seenDecls } = resolveValue(decl.value, true);
if (seenDecls.length > 0) {
const definitionDecl = getDecl(seenDecls[seenDecls.length - 1]);
docs = getAstNode(definitionDecl.src).docs;
@@ -3260,7 +3264,7 @@ Happy writing!
}
}
}
-
+
if (docs) {
descDom.innerHTML = markdown(shortDesc(docs));
} else {
@@ -3273,13 +3277,13 @@ Happy writing!
}
domSectTypes.classList.remove("hidden");
}
-
+
if (namespacesWithDocsList.length !== 0) {
const splitPoint = Math.ceil(namespacesWithDocsList.length / 2);
const template = '';
resizeDomList(domListNamespacesLeft, splitPoint, template);
- resizeDomList(domListNamespacesRight,
- namespacesWithDocsList.length - splitPoint,
+ resizeDomList(domListNamespacesRight,
+ namespacesWithDocsList.length - splitPoint,
template);
let activeList = domListNamespacesLeft;
@@ -3291,7 +3295,7 @@ Happy writing!
aDom.textContent = decl.name;
aDom.setAttribute("href", navLinkDecl(decl.name));
-
+
let descDom = liDom.children[1];
descDom.innerHTML = markdown(shortDesc(docs));
if (i == splitPoint - 1) {
@@ -3329,7 +3333,7 @@ Happy writing!
}
-
+
if (errSetsList.length !== 0) {
resizeDomList(
@@ -3690,7 +3694,7 @@ Happy writing!
scrollHistory[curNav.hash] = scrollMonitor.map(function (x) {
return [x, x.scrollTop]
});
-
+
if (skipNextHashChange == decodeURIComponent(location.hash)) {
skipNextHashChange = null;
return;
@@ -3951,15 +3955,15 @@ Happy writing!
}
function declLinkOrSrcLink(index) {
-
+
let match = getCanonDeclPath(index);
if (match) return navLink(match.modNames, match.declNames);
// could not find a precomputed decl path
const decl = getDecl(index);
-
+
// try to find a public decl by scanning declRefs and declPaths
- let value = decl.value;
+ let value = decl.value;
let i = 0;
while (true) {
i += 1;
@@ -3975,7 +3979,7 @@ Happy writing!
if ("declRef" in value.expr) {
let cp = canonDeclPaths[value.expr.declRef];
if (cp) return navLink(cp.modNames, cp.declNames);
-
+
value = getDecl(value.expr.declRef).value;
continue;
}
@@ -3988,22 +3992,22 @@ Happy writing!
continue;
}
- // if we got here it means that we failed
+ // if we got here it means that we failed
// produce a link to source code instead
return sourceFileLink(decl);
}
-
+
}
function getCanonDeclPath(index) {
if (canonDeclPaths == null) {
canonDeclPaths = computeCanonDeclPaths();
}
-
+
return canonDeclPaths[index];
-
+
}
function getCanonTypeDecl(index) {
@@ -4055,7 +4059,7 @@ Happy writing!
// Discover Title & TOC for this guide
{
let reader = new commonmark.Parser({smart: true});
- let ast = reader.parse(guide.body);
+ let ast = reader.parse(guide.body);
let walker = ast.walker();
let heading_idx = 0;
let event, node, doc, last, last_ul;
@@ -4067,26 +4071,26 @@ Happy writing!
continue;
}
-
+
if (node.next) {
walker.resumeAt(node.next, true);
} else {
walker.resumeAt(node, false);
}
node.unlink();
-
+
if (node.type === 'heading') {
if (node.level == 1) {
if (guide.title == null) {
let doc_node = new commonmark.Node("document", node.sourcepos);
while (node.firstChild) {
doc_node.appendChild(node.firstChild);
- }
- let writer = new commonmark.HtmlRenderer();
- let result = writer.render(doc_node);
+ }
+ let writer = new commonmark.HtmlRenderer();
+ let result = writer.render(doc_node);
guide.title = result;
}
-
+
continue; // don't index H1
}
@@ -4101,14 +4105,14 @@ Happy writing!
listItem.level = node.level;
node = listItem;
}
-
+
if (last_ul) {
// are we inside or outside of it?
let target_ul = last_ul;
while(target_ul.level > node.level) {
target_ul = target_ul.parent;
- }
+ }
while(target_ul.level < node.level) {
let ul_node = new commonmark.Node("list", node.sourcepos);
ul_node.level = target_ul.level + 1;
@@ -4126,7 +4130,7 @@ Happy writing!
ul_node.listType = "bullet";
ul_node.listStart = null;
doc.prependChild(ul_node);
-
+
while (ul_node.level < node.level) {
let current_ul_node = new commonmark.Node("list", node.sourcepos);
current_ul_node.level = ul_node.level + 1;
@@ -4142,13 +4146,13 @@ Happy writing!
}
}
}
- }
-
- let writer = new commonmark.HtmlRenderer();
- let result = writer.render(ast);
+ }
+
+ let writer = new commonmark.HtmlRenderer();
+ let result = writer.render(ast);
guide.toc = result;
}
-
+
// Index this guide
{
// let walker = guide.ast.walker();
@@ -4156,9 +4160,9 @@ Happy writing!
// while ((event = walker.next())) {
// node = event.node;
// if (event.entering == true && node.type === 'text') {
- // indexTextForGuide(j, i, node);
+ // indexTextForGuide(j, i, node);
// }
- // }
+ // }
}
}
}
@@ -4344,7 +4348,7 @@ Happy writing!
let domDotsToggleTimeout = null;
function onSearchInput(ev) {
curSearchIndex = -1;
-
+
let replaced = domSearch.value.replaceAll(".", " ")
// Ping red the help text if the user typed a dot.
@@ -4353,17 +4357,17 @@ Happy writing!
if (domDotsToggleTimeout != null) {
clearTimeout(domDotsToggleTimeout);
domDotsToggleTimeout = null;
- }
- domDotsToggleTimeout = setTimeout(function () {
- domSearchHelpSummary.classList.add("normal");
+ }
+ domDotsToggleTimeout = setTimeout(function () {
+ domSearchHelpSummary.classList.add("normal");
}, 1000);
}
-
+
replaced = replaced.replace(/ +/g, ' ');
if (replaced != domSearch.value) {
domSearch.value = replaced;
- }
-
+ }
+
startAsyncSearch();
}
@@ -4587,7 +4591,7 @@ Happy writing!
domSearchHelp.parentElement.removeChild(domSearchHelp)
);
domSectSearchNoResults.classList.remove("hidden");
-
+
domSectSearchResults.classList.add("hidden");
return;
}
@@ -4721,7 +4725,7 @@ Happy writing!
domSectSearchResults.classList.remove("hidden");
}
-
+
function renderSearchCursor() {
const searchResults = domListSearchResults.getElementsByTagName("li");
@@ -4738,21 +4742,21 @@ Happy writing!
function scrollGuidesTop(ev) {
document.getElementById("activeGuide").children[0].scrollIntoView({
behavior: "smooth",
- });
+ });
ev.preventDefault();
ev.stopPropagation();
}
document.scrollGuidesTop = scrollGuidesTop;
- function scrollToHeading(id, alreadyThere) {
+ function scrollToHeading(id, alreadyThere) {
// Don't scroll if the current location has a scrolling history.
if (scrollHistory[location.hash]) return;
const c = document.getElementById(id);
if (c && alreadyThere) {
- requestAnimationFrame(() => c.scrollIntoView({behavior: "smooth"}));
+ requestAnimationFrame(() => c.scrollIntoView({behavior: "smooth"}));
} else {
- requestAnimationFrame(() => c.scrollIntoView());
+ requestAnimationFrame(() => c.scrollIntoView());
}
return;
}
@@ -5105,7 +5109,7 @@ function RadixTree() {
}
}
- // Match was found, let's collect all other
+ // Match was found, let's collect all other
// partial matches from the subtree
let stack = [subtree_root];
let node;
@@ -5239,4 +5243,3 @@ function RadixTree() {
function slugify(str) {
return str.toLowerCase().trim().replace(/[^\w\s-]/g, '').replace(/[\s_-]+/g, '-').replace(/^-+|-+$/g, '');
}
-
diff --git a/lib/std/math.zig b/lib/std/math.zig
index d223b7948432..9fe80d80354d 100644
--- a/lib/std/math.zig
+++ b/lib/std/math.zig
@@ -916,6 +916,7 @@ fn testDivFloor() !void {
/// infinity. Returns an error on overflow or when denominator is
/// zero.
pub fn divCeil(comptime T: type, numerator: T, denominator: T) !T {
+ // TODO: Change implementation to use @divCeil.
@setRuntimeSafety(false);
if (denominator == 0) return error.DivisionByZero;
const info = @typeInfo(T);
diff --git a/lib/std/math/big/int.zig b/lib/std/math/big/int.zig
index 20041c5650ea..e7319f04256f 100644
--- a/lib/std/math/big/int.zig
+++ b/lib/std/math/big/int.zig
@@ -1062,6 +1062,72 @@ pub const Mutable = struct {
}
}
+ /// q = a / b (rem r)
+ ///
+ /// Returns the result of a / b rounded towards positive infinity.
+ /// q may alias with a or b.
+ ///
+ /// Asserts there is enough memory to store q and r.
+ /// The upper bound for r limb count is `b.limbs.len`.
+ /// The upper bound for q limb count is given by `a.limbs`.
+ ///
+ /// `limbs_buffer` is used for temporary storage. The amount required is given by `calcDivLimbsBufferLen`.
+ pub fn divCeil(
+ q: *Mutable,
+ r: *Mutable,
+ a: Const,
+ b: Const,
+ limbs_buffer: []Limb,
+ ) void {
+ const sep = a.limbs.len + 2;
+ var x = a.toMutable(limbs_buffer[0..sep]);
+ var y = b.toMutable(limbs_buffer[sep..]);
+
+ // div performs truncating division (@divTrunc) which rounds towards negative
+ // infinity if the result is positive and towards positive infinity if the result is
+ // negative.
+ div(q, r, &x, &y);
+
+ // @rem gives the remainder after @divTrunc, and is defined by:
+ // x * @divTrunc(x, y) + @rem(x, y) = x
+ // For all integers x, y with y != 0.
+ // In the following comments, a, b will be integers with a >= 0, b > 0, and we will take
+ // modCeil to be the remainder after @divCeil, defined by:
+ // x * @divCeil(x, y) + modCeil(x, y) = x
+ // For all integers x, y with y != 0.
+
+ if (a.positive != b.positive or r.eqlZero()) {
+ // In this case either the result is negative or the remainder is 0.
+ // If the result is negative then the default truncating division already rounds
+ // towards positive infinity, so no adjustment is needed.
+ // If the remainder is 0 then the division is exact and no adjustment is needed.
+ } else if (a.positive) {
+ // Both positive.
+ // We have:
+ // modCeil(a, b) != 0
+ // => @divCeil(a, b) = @divTrunc(a, b) + 1
+ // And:
+ // b * @divTrunc(a, b) + @rem(a, b) = a
+ // b * @divCeil(a, b) + modCeil(a, b) = a
+ // => b * @divTrunc(a, b) + b + modCeil(a, b) = a
+ // => modCeil(a, b) = @rem(a, b) - b
+ q.addScalar(q.toConst(), 1);
+ r.sub(r.toConst(), y.toConst());
+ } else {
+ // Both negative.
+ // We have:
+ // modCeil(-a, -b) != 0
+ // => @divCeil(-a, -b) = @divTrunc(-a, -b) + 1
+ // And:
+ // -b * @divTrunc(-a, -b) + @rem(-a, -b) = -a
+ // -b * @divCeil(-a, -b) + modCeil(-a, -b) = -a
+ // => -b * @divTrunc(-a, -b) - b + modCeil(-a, -b) = -a
+ // => modCeil(-a, -b) = @rem(-a, -b) + b
+ q.addScalar(q.toConst(), 1);
+ r.add(r.toConst(), y.toConst().abs());
+ }
+ }
+
/// q = a / b (rem r)
///
/// a / b are truncated (rounded towards -inf).
diff --git a/lib/std/zig/AstRlAnnotate.zig b/lib/std/zig/AstRlAnnotate.zig
index a43de68951c1..5c5272993f81 100644
--- a/lib/std/zig/AstRlAnnotate.zig
+++ b/lib/std/zig/AstRlAnnotate.zig
@@ -962,6 +962,7 @@ fn builtinCall(astrl: *AstRlAnnotate, block: ?*Block, ri: ResultInfo, node: Ast.
},
.div_exact,
.div_floor,
+ .div_ceil,
.div_trunc,
.mod,
.rem,
diff --git a/lib/std/zig/BuiltinFn.zig b/lib/std/zig/BuiltinFn.zig
index decb3cf7fd8e..a212993d247f 100644
--- a/lib/std/zig/BuiltinFn.zig
+++ b/lib/std/zig/BuiltinFn.zig
@@ -37,6 +37,7 @@ pub const Tag = enum {
c_va_start,
div_exact,
div_floor,
+ div_ceil,
div_trunc,
embed_file,
int_from_enum,
@@ -419,6 +420,13 @@ pub const list = list: {
.param_count = 2,
},
},
+ .{
+ "@divCeil",
+ .{
+ .tag = .div_ceil,
+ .param_count = 2,
+ },
+ },
.{
"@divTrunc",
.{
diff --git a/lib/zig.h b/lib/zig.h
index 7a1c69575a24..0647979386f5 100644
--- a/lib/zig.h
+++ b/lib/zig.h
@@ -537,6 +537,15 @@ typedef ptrdiff_t intptr_t;
static inline int##w##_t zig_div_floor_i##w(int##w##_t lhs, int##w##_t rhs) { \
return lhs / rhs + (lhs % rhs != INT##w##_C(0) ? zig_shr_i##w(lhs ^ rhs, UINT8_C(w) - UINT8_C(1)) : INT##w##_C(0)); \
} \
+\
+ static inline uint##w##_t zig_div_ceil_u##w(uint##w##_t lhs, uint##w##_t rhs) { \
+ return lhs / rhs + (lhs % rhs != UINT##w##_C(0) ? UINT##w##_C(1) : UINT##w##_C(0)); \
+ } \
+\
+ static inline int##w##_t zig_div_ceil_i##w(int##w##_t lhs, int##w##_t rhs) { \
+ return lhs / rhs + (lhs % rhs != INT##w##_C(0) \
+ ? zig_shr_i##w(lhs ^ rhs, UINT8_C(w) - UINT8_C(1)) + INT##w##_C(1) : INT##w##_C(0)); \
+ } \
\
zig_basic_operator(uint##w##_t, mod_u##w, %) \
\
@@ -1480,6 +1489,21 @@ static inline zig_i128 zig_div_floor_i128(zig_i128 lhs, zig_i128 rhs) {
return zig_add_i128(zig_div_trunc_i128(lhs, rhs), zig_make_i128(mask, (uint64_t)mask));
}
+static inline zig_u128 zig_div_ceil_u128(zig_u128 lhs, zig_u128 rhs) {
+ zig_u128 rem = zig_rem_u128(lhs, rhs);
+ uint64_t mask = zig_or_u64(zig_hi_u128(rem), zig_lo_u128(rem)) != UINT64_C(0)
+ ? UINT64_C(1) : UINT64_C(0);
+ return zig_add_u128(zig_div_trunc_u128(lhs, rhs), zig_make_u128(UINT64_C(0), mask));
+}
+
+static inline zig_i128 zig_div_ceil_i128(zig_i128 lhs, zig_i128 rhs) {
+ zig_i128 rem = zig_rem_i128(lhs, rhs);
+ int64_t mask = zig_or_u64((uint64_t)zig_hi_i128(rem), zig_lo_i128(rem)) != UINT64_C(0)
+ ? zig_shr_i64(zig_xor_i64(zig_hi_i128(lhs), zig_hi_i128(rhs)), UINT8_C(63)) + INT64_C(1)
+ : INT64_C(0);
+ return zig_add_i128(zig_div_trunc_i128(lhs, rhs), zig_make_i128(INT64_C(0), (uint64_t)mask));
+}
+
#define zig_mod_u128 zig_rem_u128
static inline zig_i128 zig_mod_i128(zig_i128 lhs, zig_i128 rhs) {
@@ -2655,6 +2679,11 @@ static inline void zig_div_floor_big(void *res, const void *lhs, const void *rhs
zig_trap();
}
+static inline void zig_div_ceil_big(void *res, const void *lhs, const void *rhs, bool is_signed, uint16_t bits) {
+ // TODO: Does this need to be implemented?
+ zig_trap();
+}
+
zig_extern void __umodei4(uint32_t *res, const uint32_t *lhs, const uint32_t *rhs, uintptr_t bits);
static inline void zig_rem_big(void *res, const void *lhs, const void *rhs, bool is_signed, uint16_t bits) {
if (!is_signed) {
@@ -3402,6 +3431,10 @@ zig_float_negate_builtin(128, zig_make_u128, (UINT64_C(1) << 63, UINT64_C(0)))
static inline zig_f##w zig_div_floor_f##w(zig_f##w lhs, zig_f##w rhs) { \
return zig_float_fn_f##w##_floor(zig_div_f##w(lhs, rhs)); \
} \
+\
+ static inline zig_f##w zig_div_ceil_f##w(zig_f##w lhs, zig_f##w rhs) { \
+ return zig_float_fn_f##w##_ceil(zig_div_f##w(lhs, rhs)); \
+ } \
\
static inline zig_f##w zig_mod_f##w(zig_f##w lhs, zig_f##w rhs) { \
return zig_sub_f##w(lhs, zig_mul_f##w(zig_div_floor_f##w(lhs, rhs), rhs)); \
diff --git a/src/Air.zig b/src/Air.zig
index bc3e31dff435..568a46fab6da 100644
--- a/src/Air.zig
+++ b/src/Air.zig
@@ -143,6 +143,13 @@ pub const Inst = struct {
div_floor,
/// Same as `div_floor` with optimized float mode.
div_floor_optimized,
+ /// Ceiling integer or float division. For integers, wrapping is undefined behavior.
+ /// Both operands are guaranteed to be the same type, and the result type
+ /// is the same as both operands.
+ /// Uses the `bin_op` field.
+ div_ceil,
+ /// Same as `div_ceil` with optimized float mode.
+ div_ceil_optimized,
/// Integer or float division.
/// If a remainder would be produced, undefined behavior occurs.
/// For integers, overflow is undefined behavior.
@@ -1292,6 +1299,7 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool)
.div_float,
.div_trunc,
.div_floor,
+ .div_ceil,
.div_exact,
.rem,
.mod,
@@ -1313,6 +1321,7 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool)
.div_float_optimized,
.div_trunc_optimized,
.div_floor_optimized,
+ .div_ceil_optimized,
.div_exact_optimized,
.rem_optimized,
.mod_optimized,
@@ -1662,6 +1671,8 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {
.div_trunc_optimized,
.div_floor,
.div_floor_optimized,
+ .div_ceil,
+ .div_ceil_optimized,
.div_exact,
.div_exact_optimized,
.rem,
diff --git a/src/AstGen.zig b/src/AstGen.zig
index f709751fde3c..9bc71aad591e 100644
--- a/src/AstGen.zig
+++ b/src/AstGen.zig
@@ -2719,6 +2719,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
.bit_reverse,
.div_exact,
.div_floor,
+ .div_ceil,
.div_trunc,
.mod,
.rem,
@@ -9277,6 +9278,7 @@ fn builtinCall(
.div_exact => return divBuiltin(gz, scope, ri, node, params[0], params[1], .div_exact),
.div_floor => return divBuiltin(gz, scope, ri, node, params[0], params[1], .div_floor),
+ .div_ceil => return divBuiltin(gz, scope, ri, node, params[0], params[1], .div_ceil),
.div_trunc => return divBuiltin(gz, scope, ri, node, params[0], params[1], .div_trunc),
.mod => return divBuiltin(gz, scope, ri, node, params[0], params[1], .mod),
.rem => return divBuiltin(gz, scope, ri, node, params[0], params[1], .rem),
diff --git a/src/Autodoc.zig b/src/Autodoc.zig
index a03ed351789d..69bce295a192 100644
--- a/src/Autodoc.zig
+++ b/src/Autodoc.zig
@@ -1878,6 +1878,7 @@ fn walkInstruction(
.has_field,
.div_exact,
.div_floor,
+ .div_ceil,
.div_trunc,
.mod,
.rem,
diff --git a/src/Liveness.zig b/src/Liveness.zig
index 56b70be317bf..9da8f16d32c4 100644
--- a/src/Liveness.zig
+++ b/src/Liveness.zig
@@ -249,6 +249,7 @@ pub fn categorizeOperand(
.div_float,
.div_trunc,
.div_floor,
+ .div_ceil,
.div_exact,
.rem,
.mod,
@@ -276,6 +277,7 @@ pub fn categorizeOperand(
.div_float_optimized,
.div_trunc_optimized,
.div_floor_optimized,
+ .div_ceil_optimized,
.div_exact_optimized,
.rem_optimized,
.mod_optimized,
@@ -905,6 +907,8 @@ fn analyzeInst(
.div_trunc_optimized,
.div_floor,
.div_floor_optimized,
+ .div_ceil,
+ .div_ceil_optimized,
.div_exact,
.div_exact_optimized,
.rem,
diff --git a/src/Liveness/Verify.zig b/src/Liveness/Verify.zig
index 0493c9322b13..1d952162331e 100644
--- a/src/Liveness/Verify.zig
+++ b/src/Liveness/Verify.zig
@@ -217,6 +217,8 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
.div_trunc_optimized,
.div_floor,
.div_floor_optimized,
+ .div_ceil,
+ .div_ceil_optimized,
.div_exact,
.div_exact_optimized,
.rem,
diff --git a/src/Sema.zig b/src/Sema.zig
index 44a04e7d256c..5ab10d02f843 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -1194,6 +1194,7 @@ fn analyzeBodyInner(
.div => try sema.zirDiv(block, inst),
.div_exact => try sema.zirDivExact(block, inst),
.div_floor => try sema.zirDivFloor(block, inst),
+ .div_ceil => try sema.zirDivCeil(block, inst),
.div_trunc => try sema.zirDivTrunc(block, inst),
.mod_rem => try sema.zirModRem(block, inst),
@@ -14853,7 +14854,7 @@ fn zirDiv(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins
return sema.fail(
block,
src,
- "division with '{}' and '{}': signed integers must use @divTrunc, @divFloor, or @divExact",
+ "division with '{}' and '{}': signed integers must use @divTrunc, @divFloor, @divCeil, or @divExact",
.{ lhs_ty.fmt(mod), rhs_ty.fmt(mod) },
);
}
@@ -15142,6 +15143,117 @@ fn zirDivFloor(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
return block.addBinOp(airTag(block, is_int, .div_floor, .div_floor_optimized), casted_lhs, casted_rhs);
}
+fn zirDivCeil(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
+ const mod = sema.mod;
+ const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
+ const src: LazySrcLoc = .{ .node_offset_bin_op = inst_data.src_node };
+ sema.src = src;
+ const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node };
+ const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node };
+ const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
+ const lhs = try sema.resolveInst(extra.lhs);
+ const rhs = try sema.resolveInst(extra.rhs);
+ const lhs_ty = sema.typeOf(lhs);
+ const rhs_ty = sema.typeOf(rhs);
+ const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison(mod);
+ const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison(mod);
+ try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
+ try sema.checkInvalidPtrArithmetic(block, src, lhs_ty);
+
+ const instructions = &[_]Air.Inst.Ref{ lhs, rhs };
+ const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{
+ .override = &[_]?LazySrcLoc{ lhs_src, rhs_src },
+ });
+
+ const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
+ const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
+
+ const lhs_scalar_ty = lhs_ty.scalarType(mod);
+ const rhs_scalar_ty = rhs_ty.scalarType(mod);
+ const scalar_tag = resolved_type.scalarType(mod).zigTypeTag(mod);
+
+ const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt;
+
+ try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .div_ceil);
+
+ const maybe_lhs_val = try sema.resolveValueIntable(casted_lhs);
+ const maybe_rhs_val = try sema.resolveValueIntable(casted_rhs);
+
+ const runtime_src = rs: {
+ // For integers:
+ // If the lhs is zero, then zero is returned regardless of rhs.
+ // If the rhs is zero, compile error for division by zero.
+ // If the rhs is undefined, compile error because there is a possible
+ // value (zero) for which the division would be illegal behavior.
+ // If the lhs is undefined:
+ // * if lhs type is signed:
+ // * if rhs is comptime-known and not -1, result is undefined
+ // * if rhs is -1 or runtime-known, compile error because there is a
+ // possible value (-min_int / -1) for which division would be
+ // illegal behavior.
+ // * if lhs type is unsigned, undef is returned regardless of rhs.
+ // TODO: emit runtime safety for division by zero
+ //
+ // For floats:
+ // If the rhs is zero, compile error for division by zero.
+ // If the rhs is undefined, compile error because there is a possible
+ // value (zero) for which the division would be illegal behavior.
+ // If the lhs is undefined, result is undefined.
+ if (maybe_lhs_val) |lhs_val| {
+ if (!lhs_val.isUndef(mod)) {
+ if (try lhs_val.compareAllWithZeroAdvanced(.eq, sema)) {
+ const scalar_zero = switch (scalar_tag) {
+ .ComptimeFloat, .Float => try mod.floatValue(resolved_type.scalarType(mod), 0.0),
+ .ComptimeInt, .Int => try mod.intValue(resolved_type.scalarType(mod), 0),
+ else => unreachable,
+ };
+ const zero_val = try sema.splat(resolved_type, scalar_zero);
+ return Air.internedToRef(zero_val.toIntern());
+ }
+ }
+ }
+ if (maybe_rhs_val) |rhs_val| {
+ if (rhs_val.isUndef(mod)) {
+ return sema.failWithUseOfUndef(block, rhs_src);
+ }
+ if (!(try rhs_val.compareAllWithZeroAdvanced(.neq, sema))) {
+ return sema.failWithDivideByZero(block, rhs_src);
+ }
+ // TODO: if the RHS is one, return the LHS directly
+ }
+ if (maybe_lhs_val) |lhs_val| {
+ if (lhs_val.isUndef(mod)) {
+ if (lhs_scalar_ty.isSignedInt(mod) and rhs_scalar_ty.isSignedInt(mod)) {
+ if (maybe_rhs_val) |rhs_val| {
+ if (try sema.compareAll(rhs_val, .neq, try mod.intValue(resolved_type, -1), resolved_type)) {
+ return mod.undefRef(resolved_type);
+ }
+ }
+ return sema.failWithUseOfUndef(block, rhs_src);
+ }
+ return mod.undefRef(resolved_type);
+ }
+
+ if (maybe_rhs_val) |rhs_val| {
+ if (is_int) {
+ return Air.internedToRef((try lhs_val.intDivCeil(rhs_val, resolved_type, sema.arena, mod)).toIntern());
+ } else {
+ return Air.internedToRef((try lhs_val.floatDivCeil(rhs_val, resolved_type, sema.arena, mod)).toIntern());
+ }
+ } else break :rs rhs_src;
+ } else break :rs lhs_src;
+ };
+
+ try sema.requireRuntimeBlock(block, src, runtime_src);
+
+ if (block.wantSafety()) {
+ try sema.addDivIntOverflowSafety(block, src, resolved_type, lhs_scalar_ty, maybe_lhs_val, maybe_rhs_val, casted_lhs, casted_rhs, is_int);
+ try sema.addDivByZeroSafety(block, src, resolved_type, maybe_rhs_val, casted_rhs, is_int);
+ }
+
+ return block.addBinOp(airTag(block, is_int, .div_ceil, .div_ceil_optimized), casted_lhs, casted_rhs);
+}
+
fn zirDivTrunc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const mod = sema.mod;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
@@ -19359,7 +19471,7 @@ fn analyzeRet(
fn floatOpAllowed(tag: Zir.Inst.Tag) bool {
// extend this swich as additional operators are implemented
return switch (tag) {
- .add, .sub, .mul, .div, .div_exact, .div_trunc, .div_floor, .mod, .rem, .mod_rem => true,
+ .add, .sub, .mul, .div, .div_exact, .div_trunc, .div_floor, .div_ceil, .mod, .rem, .mod_rem => true,
else => false,
};
}
diff --git a/src/Zir.zig b/src/Zir.zig
index 3737467e47be..1ef3c5d7c7b7 100644
--- a/src/Zir.zig
+++ b/src/Zir.zig
@@ -195,6 +195,9 @@ pub const Inst = struct {
/// Implements the `@divFloor` builtin.
/// Uses the `pl_node` union field with payload `Bin`.
div_floor,
+ /// Implements the `@divCeil` builtin.
+ /// Uses the `pl_node` union field with payload `Bin`.
+ div_ceil,
/// Implements the `@divTrunc` builtin.
/// Uses the `pl_node` union field with payload `Bin`.
div_trunc,
@@ -1239,6 +1242,7 @@ pub const Inst = struct {
.bit_reverse,
.div_exact,
.div_floor,
+ .div_ceil,
.div_trunc,
.mod,
.rem,
@@ -1534,6 +1538,7 @@ pub const Inst = struct {
.bit_reverse,
.div_exact,
.div_floor,
+ .div_ceil,
.div_trunc,
.mod,
.rem,
@@ -1804,6 +1809,7 @@ pub const Inst = struct {
.div_exact = .pl_node,
.div_floor = .pl_node,
+ .div_ceil = .pl_node,
.div_trunc = .pl_node,
.mod = .pl_node,
.rem = .pl_node,
diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig
index ee5e58ae05e9..c49f20fc6e89 100644
--- a/src/arch/aarch64/CodeGen.zig
+++ b/src/arch/aarch64/CodeGen.zig
@@ -687,6 +687,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.div_float => try self.airBinOp(inst, .div_float),
.div_trunc => try self.airBinOp(inst, .div_trunc),
.div_floor => try self.airBinOp(inst, .div_floor),
+ .div_ceil => try self.airBinOp(inst, .div_ceil),
.div_exact => try self.airBinOp(inst, .div_exact),
.rem => try self.airBinOp(inst, .rem),
.mod => try self.airBinOp(inst, .mod),
@@ -871,6 +872,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.div_float_optimized,
.div_trunc_optimized,
.div_floor_optimized,
+ .div_ceil_optimized,
.div_exact_optimized,
.rem_optimized,
.mod_optimized,
@@ -2072,6 +2074,22 @@ fn divFloor(
}
}
+fn divCeil(
+ self: *Self,
+ lhs_bind: ReadArg.Bind,
+ rhs_bind: ReadArg.Bind,
+ lhs_ty: Type,
+ rhs_ty: Type,
+ maybe_inst: ?Air.Inst.Index,
+) InnerError!MCValue {
+ _ = maybe_inst;
+ _ = rhs_ty;
+ _ = lhs_ty;
+ _ = rhs_bind;
+ _ = lhs_bind;
+ return self.fail("TODO: implement `@divCeil` for {}", .{self.target.cpu.arch});
+}
+
fn divExact(
self: *Self,
lhs_bind: ReadArg.Bind,
@@ -2449,6 +2467,8 @@ fn airBinOp(self: *Self, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void {
.div_floor => try self.divFloor(lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst),
+ .div_ceil => try self.divCeil(lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst),
+
.div_exact => try self.divExact(lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst),
.rem => try self.rem(lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst),
diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig
index 7227347e1b9e..d25a03e02101 100644
--- a/src/arch/arm/CodeGen.zig
+++ b/src/arch/arm/CodeGen.zig
@@ -673,6 +673,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.div_float => try self.airBinOp(inst, .div_float),
.div_trunc => try self.airBinOp(inst, .div_trunc),
.div_floor => try self.airBinOp(inst, .div_floor),
+ .div_ceil => try self.airBinOp(inst, .div_ceil),
.div_exact => try self.airBinOp(inst, .div_exact),
.rem => try self.airBinOp(inst, .rem),
.mod => try self.airBinOp(inst, .mod),
@@ -857,6 +858,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.div_float_optimized,
.div_trunc_optimized,
.div_floor_optimized,
+ .div_ceil_optimized,
.div_exact_optimized,
.rem_optimized,
.mod_optimized,
@@ -1519,6 +1521,8 @@ fn airBinOp(self: *Self, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void {
.div_floor => try self.divFloor(lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst),
+ .div_ceil => try self.divCeil(lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst),
+
.div_exact => try self.divExact(lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst),
.rem => try self.rem(lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst),
@@ -3578,6 +3582,22 @@ fn divFloor(
}
}
+fn divCeil(
+ self: *Self,
+ lhs_bind: ReadArg.Bind,
+ rhs_bind: ReadArg.Bind,
+ lhs_ty: Type,
+ rhs_ty: Type,
+ maybe_inst: ?Air.Inst.Index,
+) InnerError!MCValue {
+ _ = maybe_inst;
+ _ = rhs_ty;
+ _ = lhs_ty;
+ _ = rhs_bind;
+ _ = lhs_bind;
+ return self.fail("TODO: implement `@divCeil` for {}", .{self.target.cpu.arch});
+}
+
fn divExact(
self: *Self,
lhs_bind: ReadArg.Bind,
diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig
index 7d3b0cd1a0ac..5f2ffb732924 100644
--- a/src/arch/riscv64/CodeGen.zig
+++ b/src/arch/riscv64/CodeGen.zig
@@ -534,7 +534,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.mul_with_overflow => try self.airMulWithOverflow(inst),
.shl_with_overflow => try self.airShlWithOverflow(inst),
- .div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst),
+ .div_float, .div_trunc, .div_floor, .div_ceil, .div_exact => try self.airDiv(inst),
.cmp_lt => try self.airCmp(inst, .lt),
.cmp_lte => try self.airCmp(inst, .lte),
@@ -690,6 +690,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.div_float_optimized,
.div_trunc_optimized,
.div_floor_optimized,
+ .div_ceil_optimized,
.div_exact_optimized,
.rem_optimized,
.mod_optimized,
diff --git a/src/arch/sparc64/CodeGen.zig b/src/arch/sparc64/CodeGen.zig
index e7db3be8b020..055340a2c7fa 100644
--- a/src/arch/sparc64/CodeGen.zig
+++ b/src/arch/sparc64/CodeGen.zig
@@ -555,7 +555,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.mul_with_overflow => try self.airMulWithOverflow(inst),
.shl_with_overflow => try self.airShlWithOverflow(inst),
- .div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst),
+ .div_float, .div_trunc, .div_floor, .div_ceil, .div_exact => try self.airDiv(inst),
.cmp_lt => try self.airCmp(inst, .lt),
.cmp_lte => try self.airCmp(inst, .lte),
@@ -703,6 +703,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.div_float_optimized,
.div_trunc_optimized,
.div_floor_optimized,
+ .div_ceil_optimized,
.div_exact_optimized,
.rem_optimized,
.mod_optimized,
diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig
index da33ae521d49..2cbdc0a67144 100644
--- a/src/arch/wasm/CodeGen.zig
+++ b/src/arch/wasm/CodeGen.zig
@@ -1847,6 +1847,7 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
.div_float, .div_exact => func.airDiv(inst),
.div_trunc => func.airDivTrunc(inst),
.div_floor => func.airDivFloor(inst),
+ .div_ceil => func.airDivCeil(inst),
.bit_and => func.airBinOp(inst, .@"and"),
.bit_or => func.airBinOp(inst, .@"or"),
.bool_and => func.airBinOp(inst, .@"and"),
@@ -2049,6 +2050,7 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
.div_float_optimized,
.div_trunc_optimized,
.div_floor_optimized,
+ .div_ceil_optimized,
.div_exact_optimized,
.rem_optimized,
.mod_optimized,
@@ -6810,6 +6812,124 @@ fn airDivFloor(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs });
}
+fn airDivCeil(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
+ const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
+
+ const mod = func.bin_file.base.comp.module.?;
+ const ty = func.typeOfIndex(inst);
+ const lhs = try func.resolveInst(bin_op.lhs);
+ const rhs = try func.resolveInst(bin_op.rhs);
+
+ if (ty.isUnsignedInt(mod)) {
+ const int_bits = ty.intInfo(mod).bits;
+ const wasm_bits = toWasmBits(int_bits) orelse {
+ return func.fail("TODO: `@divCeil` for unsigned integers larger than 64 bits ({d} bits requested)", .{int_bits});
+ };
+
+ if (wasm_bits > 64) {
+ return func.fail("TODO: `@divCeil` for unsigned integers larger than 64 bits ({d} bits requested)", .{int_bits});
+ }
+
+ _ = try func.binOp(lhs, rhs, ty, .div);
+ _ = try func.binOp(lhs, rhs, ty, .rem);
+
+ switch (wasm_bits) {
+ 32 => {
+ _ = try func.cmp(.stack, WValue{ .imm32 = 0 }, ty, .neq);
+ try func.addTag(.i32_add);
+ },
+ 64 => {
+ _ = try func.cmp(.stack, WValue{ .imm64 = 0 }, ty, .neq);
+ try func.addTag(.i64_extend_i32_u);
+ try func.addTag(.i64_add);
+ },
+ else => unreachable,
+ }
+ } else if (ty.isSignedInt(mod)) {
+ const int_bits = ty.intInfo(mod).bits;
+ const wasm_bits = toWasmBits(int_bits) orelse {
+ return func.fail("TODO: `@divCeil` for signed integers larger than 64 bits ({d} bits requested)", .{int_bits});
+ };
+
+ if (wasm_bits > 64) {
+ return func.fail("TODO: `@divCeil` for signed integers larger than 64 bits ({d} bits requested)", .{int_bits});
+ }
+
+ const lhs_wasm = if (wasm_bits != int_bits)
+ try (try func.signExtendInt(lhs, ty)).toLocal(func, ty)
+ else
+ lhs;
+
+ const rhs_wasm = if (wasm_bits != int_bits)
+ try (try func.signExtendInt(rhs, ty)).toLocal(func, ty)
+ else
+ rhs;
+
+ const zero = switch (wasm_bits) {
+ 32 => WValue{ .imm32 = 0 },
+ 64 => WValue{ .imm64 = 0 },
+ else => unreachable,
+ };
+
+ _ = try func.binOp(lhs_wasm, rhs_wasm, ty, .div);
+
+ // 1 if lhs and rhs have the same sign, 0 otherwise.
+ _ = try func.binOp(lhs_wasm, rhs_wasm, ty, .xor);
+ _ = try func.cmp(.stack, zero, ty, .gte);
+
+ _ = try func.binOp(lhs_wasm, rhs_wasm, ty, .rem);
+ _ = try func.cmp(.stack, zero, ty, .neq);
+
+ try func.addTag(.i32_and);
+
+ // Comparisons produce 32 bit integers, which must be extended in the 64 bit case.
+ if (wasm_bits == 64) {
+ try func.addTag(.i64_extend_i32_u);
+ }
+
+ _ = try func.binOp(.stack, .stack, ty, .add);
+
+ // We need to zero the high bits because N bit comparisons consider all 32 or 64 bits, and
+ // expect all but the lowest N bits to be 0.
+ // TODO: Should we be zeroing the high bits here or should we be ignoring the high bits
+ // when performing comparisons?
+ if (wasm_bits != int_bits) {
+ _ = try func.wrapOperand(.stack, ty);
+ }
+ } else {
+ const float_bits = ty.floatBits(func.target);
+ if (float_bits > 64) {
+ return func.fail("TODO: `@divCeil` for floats larger than 64 bits ({d} bits requested)", .{float_bits});
+ }
+ const is_f16 = float_bits == 16;
+ const lhs_wasm = if (is_f16) try func.fpext(lhs, Type.f16, Type.f32) else lhs;
+ const rhs_wasm = if (is_f16) try func.fpext(rhs, Type.f16, Type.f32) else rhs;
+
+ try func.emitWValue(lhs_wasm);
+ try func.emitWValue(rhs_wasm);
+
+ switch (float_bits) {
+ 16, 32 => {
+ try func.addTag(.f32_div);
+ try func.addTag(.f32_ceil);
+ },
+ 64 => {
+ try func.addTag(.f64_div);
+ try func.addTag(.f64_ceil);
+ },
+ else => unreachable,
+ }
+
+ if (is_f16) {
+ _ = try func.fptrunc(.stack, Type.f32, Type.f16);
+ }
+ }
+
+ const result = try func.allocLocal(ty);
+ try func.addLabel(.local_set, result.local.value);
+ func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs });
+}
+
fn divSigned(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type) InnerError!WValue {
const mod = func.bin_file.base.comp.module.?;
const int_bits = ty.intInfo(mod).bits;
diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig
index 55e241cbd4c3..3a3b4e7c51e9 100644
--- a/src/arch/x86_64/CodeGen.zig
+++ b/src/arch/x86_64/CodeGen.zig
@@ -1977,7 +1977,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.mul_with_overflow => try self.airMulWithOverflow(inst),
.shl_with_overflow => try self.airShlWithOverflow(inst),
- .div_float, .div_trunc, .div_floor, .div_exact => try self.airMulDivBinOp(inst),
+ .div_float, .div_trunc, .div_floor, .div_ceil, .div_exact => try self.airMulDivBinOp(inst),
.cmp_lt => try self.airCmp(inst, .lt),
.cmp_lte => try self.airCmp(inst, .lte),
@@ -2124,6 +2124,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.div_float_optimized,
.div_trunc_optimized,
.div_floor_optimized,
+ .div_ceil_optimized,
.div_exact_optimized,
.rem_optimized,
.mod_optimized,
@@ -3243,7 +3244,7 @@ fn airMulDivBinOp(self: *Self, inst: Air.Inst.Index) !void {
self.activeIntBits(bin_op.rhs),
dst_info.bits / 2,
),
- .div_trunc, .div_floor, .div_exact, .rem, .mod => dst_info.bits,
+ .div_trunc, .div_floor, .div_ceil, .div_exact, .rem, .mod => dst_info.bits,
});
const src_abi_size: u32 = @intCast(src_ty.abiSize(mod));
@@ -3438,6 +3439,7 @@ fn airMulDivBinOp(self: *Self, inst: Air.Inst.Index) !void {
else => call_mcv,
} else call_mcv;
},
+ .div_ceil => return self.fail("TODO: implement `@divCeil` on {} and {} for {}", .{ dst_ty.fmt(mod), src_ty.fmt(mod), self.target.cpu.arch }),
};
try self.spillEflagsIfOccupied();
@@ -8138,7 +8140,7 @@ fn genMulDivBinOp(
if (switch (tag) {
else => unreachable,
.mul, .mul_wrap => dst_abi_size != src_abi_size and dst_abi_size != src_abi_size * 2,
- .div_trunc, .div_floor, .div_exact, .rem, .mod => dst_abi_size != src_abi_size,
+ .div_trunc, .div_floor, .div_ceil, .div_exact, .rem, .mod => dst_abi_size != src_abi_size,
} or src_abi_size > 8) return self.fail(
"TODO implement genMulDivBinOp for {s} from {} to {}",
.{ @tagName(tag), src_ty.fmt(mod), dst_ty.fmt(mod) },
@@ -8290,6 +8292,8 @@ fn genMulDivBinOp(
}
},
+ .div_ceil => return self.fail("TODO: implement `@divCeil` on {} and {} for {}", .{ dst_ty.fmt(mod), src_ty.fmt(mod), self.target.cpu.arch }),
+
else => unreachable,
}
}
@@ -8326,6 +8330,7 @@ fn genBinOp(
.div_float,
.div_trunc,
.div_floor,
+ .div_ceil,
=> std.fmt.bufPrint(&callee_buf, "__{s}{c}f3", .{
@tagName(air_tag)[0..3],
floatCompilerRtAbiName(float_bits),
@@ -8463,10 +8468,11 @@ fn genBinOp(
.callee = callee,
} }, &.{ lhs_ty, rhs_ty }, &.{ adjusted, .{ .air_ref = rhs_air } });
},
- .div_trunc, .div_floor => try self.genRoundLibcall(lhs_ty, result, .{
+ .div_trunc, .div_floor, .div_ceil => try self.genRoundLibcall(lhs_ty, result, .{
.mode = switch (air_tag) {
.div_trunc => .zero,
.div_floor => .down,
+ .div_ceil => .up,
else => unreachable,
},
.precision = .inexact,
@@ -8895,7 +8901,7 @@ fn genBinOp(
.add => .{ .v_ss, .add },
.sub => .{ .v_ss, .sub },
.mul => .{ .v_ss, .mul },
- .div_float, .div_trunc, .div_floor, .div_exact => .{ .v_ss, .div },
+ .div_float, .div_trunc, .div_floor, .div_ceil, .div_exact => .{ .v_ss, .div },
.max => .{ .v_ss, .max },
.min => .{ .v_ss, .max },
else => unreachable,
@@ -8905,7 +8911,7 @@ fn genBinOp(
tmp_reg,
);
switch (air_tag) {
- .div_trunc, .div_floor => try self.asmRegisterRegisterRegisterImmediate(
+ .div_trunc, .div_floor, .div_ceil => try self.asmRegisterRegisterRegisterImmediate(
.{ .v_ss, .round },
dst_reg,
dst_reg,
@@ -8914,6 +8920,7 @@ fn genBinOp(
.mode = switch (air_tag) {
.div_trunc => .zero,
.div_floor => .down,
+ .div_ceil => .up,
else => unreachable,
},
.precision = .inexact,
@@ -8936,6 +8943,7 @@ fn genBinOp(
.div_float,
.div_trunc,
.div_floor,
+ .div_ceil,
.div_exact,
=> if (self.hasFeature(.avx)) .{ .v_ss, .div } else .{ ._ss, .div },
.max => if (self.hasFeature(.avx)) .{ .v_ss, .max } else .{ ._ss, .max },
@@ -8949,6 +8957,7 @@ fn genBinOp(
.div_float,
.div_trunc,
.div_floor,
+ .div_ceil,
.div_exact,
=> if (self.hasFeature(.avx)) .{ .v_sd, .div } else .{ ._sd, .div },
.max => if (self.hasFeature(.avx)) .{ .v_sd, .max } else .{ ._sd, .max },
@@ -9341,7 +9350,7 @@ fn genBinOp(
.add => .{ .v_ss, .add },
.sub => .{ .v_ss, .sub },
.mul => .{ .v_ss, .mul },
- .div_float, .div_trunc, .div_floor, .div_exact => .{ .v_ss, .div },
+ .div_float, .div_trunc, .div_floor, .div_ceil, .div_exact => .{ .v_ss, .div },
.max => .{ .v_ss, .max },
.min => .{ .v_ss, .max },
else => unreachable,
@@ -9392,7 +9401,7 @@ fn genBinOp(
.add => .{ .v_ps, .add },
.sub => .{ .v_ps, .sub },
.mul => .{ .v_ps, .mul },
- .div_float, .div_trunc, .div_floor, .div_exact => .{ .v_ps, .div },
+ .div_float, .div_trunc, .div_floor, .div_ceil, .div_exact => .{ .v_ps, .div },
.max => .{ .v_ps, .max },
.min => .{ .v_ps, .max },
else => unreachable,
@@ -9435,7 +9444,7 @@ fn genBinOp(
.add => .{ .v_ps, .add },
.sub => .{ .v_ps, .sub },
.mul => .{ .v_ps, .mul },
- .div_float, .div_trunc, .div_floor, .div_exact => .{ .v_ps, .div },
+ .div_float, .div_trunc, .div_floor, .div_ceil, .div_exact => .{ .v_ps, .div },
.max => .{ .v_ps, .max },
.min => .{ .v_ps, .max },
else => unreachable,
@@ -9478,7 +9487,7 @@ fn genBinOp(
.add => .{ .v_ps, .add },
.sub => .{ .v_ps, .sub },
.mul => .{ .v_ps, .mul },
- .div_float, .div_trunc, .div_floor, .div_exact => .{ .v_ps, .div },
+ .div_float, .div_trunc, .div_floor, .div_ceil, .div_exact => .{ .v_ps, .div },
.max => .{ .v_ps, .max },
.min => .{ .v_ps, .max },
else => unreachable,
@@ -9506,6 +9515,7 @@ fn genBinOp(
.div_float,
.div_trunc,
.div_floor,
+ .div_ceil,
.div_exact,
=> if (self.hasFeature(.avx)) .{ .v_ss, .div } else .{ ._ss, .div },
.max => if (self.hasFeature(.avx)) .{ .v_ss, .max } else .{ ._ss, .max },
@@ -9526,6 +9536,7 @@ fn genBinOp(
.div_float,
.div_trunc,
.div_floor,
+ .div_ceil,
.div_exact,
=> if (self.hasFeature(.avx)) .{ .v_ps, .div } else .{ ._ps, .div },
.max => if (self.hasFeature(.avx)) .{ .v_ps, .max } else .{ ._ps, .max },
@@ -9543,7 +9554,7 @@ fn genBinOp(
.add => .{ .v_ps, .add },
.sub => .{ .v_ps, .sub },
.mul => .{ .v_ps, .mul },
- .div_float, .div_trunc, .div_floor, .div_exact => .{ .v_ps, .div },
+ .div_float, .div_trunc, .div_floor, .div_ceil, .div_exact => .{ .v_ps, .div },
.max => .{ .v_ps, .max },
.min => .{ .v_ps, .min },
.cmp_lt, .cmp_lte, .cmp_eq, .cmp_gte, .cmp_gt, .cmp_neq => .{ .v_ps, .cmp },
@@ -9559,6 +9570,7 @@ fn genBinOp(
.div_float,
.div_trunc,
.div_floor,
+ .div_ceil,
.div_exact,
=> if (self.hasFeature(.avx)) .{ .v_sd, .div } else .{ ._sd, .div },
.max => if (self.hasFeature(.avx)) .{ .v_sd, .max } else .{ ._sd, .max },
@@ -9579,6 +9591,7 @@ fn genBinOp(
.div_float,
.div_trunc,
.div_floor,
+ .div_ceil,
.div_exact,
=> if (self.hasFeature(.avx)) .{ .v_pd, .div } else .{ ._pd, .div },
.max => if (self.hasFeature(.avx)) .{ .v_pd, .max } else .{ ._pd, .max },
@@ -9596,7 +9609,7 @@ fn genBinOp(
.add => .{ .v_pd, .add },
.sub => .{ .v_pd, .sub },
.mul => .{ .v_pd, .mul },
- .div_float, .div_trunc, .div_floor, .div_exact => .{ .v_pd, .div },
+ .div_float, .div_trunc, .div_floor, .div_ceil, .div_exact => .{ .v_pd, .div },
.max => .{ .v_pd, .max },
.cmp_lt, .cmp_lte, .cmp_eq, .cmp_gte, .cmp_gt, .cmp_neq => .{ .v_pd, .cmp },
.min => .{ .v_pd, .min },
@@ -9713,10 +9726,11 @@ fn genBinOp(
switch (air_tag) {
.add, .add_wrap, .sub, .sub_wrap, .mul, .mul_wrap, .div_float, .div_exact => {},
- .div_trunc, .div_floor => try self.genRound(lhs_ty, dst_reg, .{ .register = dst_reg }, .{
+ .div_trunc, .div_floor, .div_ceil => try self.genRound(lhs_ty, dst_reg, .{ .register = dst_reg }, .{
.mode = switch (air_tag) {
.div_trunc => .zero,
.div_floor => .down,
+ .div_ceil => .up,
else => unreachable,
},
.precision = .inexact,
diff --git a/src/codegen/c.zig b/src/codegen/c.zig
index 7fd367bb4993..4cd62281d5b2 100644
--- a/src/codegen/c.zig
+++ b/src/codegen/c.zig
@@ -3137,6 +3137,7 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,
try airBinFloatOp(f, inst, "fmod");
},
.div_floor => try airBinBuiltinCall(f, inst, "div_floor", .none),
+ .div_ceil => try airBinBuiltinCall(f, inst, "div_ceil", .none),
.mod => try airBinBuiltinCall(f, inst, "mod", .none),
.abs => try airAbs(f, inst),
@@ -3332,6 +3333,7 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,
.div_float_optimized,
.div_trunc_optimized,
.div_floor_optimized,
+ .div_ceil_optimized,
.div_exact_optimized,
.rem_optimized,
.mod_optimized,
diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig
index 10323b688c2e..c0e1b89b7367 100644
--- a/src/codegen/llvm.zig
+++ b/src/codegen/llvm.zig
@@ -4978,6 +4978,7 @@ pub const FuncGen = struct {
.div_float => try self.airDivFloat(inst, .normal),
.div_trunc => try self.airDivTrunc(inst, .normal),
.div_floor => try self.airDivFloor(inst, .normal),
+ .div_ceil => try self.airDivCeil(inst, .normal),
.div_exact => try self.airDivExact(inst, .normal),
.rem => try self.airRem(inst, .normal),
.mod => try self.airMod(inst, .normal),
@@ -4995,6 +4996,7 @@ pub const FuncGen = struct {
.div_float_optimized => try self.airDivFloat(inst, .fast),
.div_trunc_optimized => try self.airDivTrunc(inst, .fast),
.div_floor_optimized => try self.airDivFloor(inst, .fast),
+ .div_ceil_optimized => try self.airDivCeil(inst, .fast),
.div_exact_optimized => try self.airDivExact(inst, .fast),
.rem_optimized => try self.airRem(inst, .fast),
.mod_optimized => try self.airMod(inst, .fast),
@@ -7925,6 +7927,56 @@ pub const FuncGen = struct {
return self.wip.bin(.udiv, lhs, rhs, "");
}
+ fn airDivCeil(self: *FuncGen, inst: Air.Inst.Index, fast: Builder.FastMathKind) !Builder.Value {
+ const o = self.dg.object;
+ const mod = o.module;
+ const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
+ const lhs = try self.resolveInst(bin_op.lhs);
+ const rhs = try self.resolveInst(bin_op.rhs);
+ const inst_ty = self.typeOfIndex(inst);
+ const scalar_ty = inst_ty.scalarType(mod);
+
+ if (scalar_ty.isRuntimeFloat()) {
+ const result = try self.buildFloatOp(.div, fast, inst_ty, 2, .{ lhs, rhs });
+ return self.buildFloatOp(.ceil, fast, inst_ty, 1, .{result});
+ } else if (scalar_ty.isSignedInt(mod)) {
+ const inst_llvm_ty = try o.lowerType(inst_ty);
+ const bit_size_minus_one = try o.builder.splatValue(inst_llvm_ty, try o.builder.intConst(
+ inst_llvm_ty.scalarType(&o.builder),
+ inst_llvm_ty.scalarBits(&o.builder) - 1,
+ ));
+ const zero = try o.builder.zeroInitValue(inst_llvm_ty);
+ const one = try o.builder.splatValue(inst_llvm_ty, try o.builder.intConst(inst_llvm_ty.scalarType(&o.builder), 1));
+
+ const quotient = try self.wip.bin(.sdiv, lhs, rhs, "");
+ const rem = try self.wip.bin(.srem, lhs, rhs, "");
+
+ const quotient_sign = try self.wip.bin(.xor, lhs, rhs, "");
+ // quotient_sign_mask has all bits set to 1 if the result is negative, or 0
+ // otherwise.
+ const quotient_sign_mask = try self.wip.bin(.ashr, quotient_sign, bit_size_minus_one, "");
+ // correction_if_inexact is 0 if the result is negative, 1 otherwise.
+ const correction_if_inexact = try self.wip.bin(.add, quotient_sign_mask, one, "");
+ const is_rem_nonzero = try self.wip.icmp(.ne, rem, zero, "");
+ const correction = try self.wip.select(fast, is_rem_nonzero, correction_if_inexact, zero, "");
+
+ return self.wip.bin(.@"add nsw", quotient, correction, "");
+ } else if (scalar_ty.isUnsignedInt(mod)) {
+ const inst_llvm_ty = try o.lowerType(inst_ty);
+ const zero = try o.builder.zeroInitValue(inst_llvm_ty);
+ const one = try o.builder.splatValue(inst_llvm_ty, try o.builder.intConst(inst_llvm_ty.scalarType(&o.builder), 1));
+
+ const quotient = try self.wip.bin(.udiv, lhs, rhs, "");
+ const rem = try self.wip.bin(.urem, lhs, rhs, "");
+ const is_non_zero = try self.wip.icmp(.ne, rem, zero, "");
+ const correction = try self.wip.select(fast, is_non_zero, one, zero, "");
+
+ return try self.wip.bin(.@"add nuw", quotient, correction, "");
+ } else {
+ unreachable;
+ }
+ }
+
fn airDivExact(self: *FuncGen, inst: Air.Inst.Index, fast: Builder.FastMathKind) !Builder.Value {
const o = self.dg.object;
const mod = o.module;
diff --git a/src/print_air.zig b/src/print_air.zig
index 7cc09e9f992c..f979a7161853 100644
--- a/src/print_air.zig
+++ b/src/print_air.zig
@@ -120,6 +120,7 @@ const Writer = struct {
.div_float,
.div_trunc,
.div_floor,
+ .div_ceil,
.div_exact,
.rem,
.mod,
@@ -150,6 +151,7 @@ const Writer = struct {
.div_float_optimized,
.div_trunc_optimized,
.div_floor_optimized,
+ .div_ceil_optimized,
.div_exact_optimized,
.rem_optimized,
.mod_optimized,
diff --git a/src/print_zir.zig b/src/print_zir.zig
index a6e3ca91a89d..5074c9dd6a2a 100644
--- a/src/print_zir.zig
+++ b/src/print_zir.zig
@@ -406,6 +406,7 @@ const Writer = struct {
.truncate,
.div_exact,
.div_floor,
+ .div_ceil,
.div_trunc,
.mod,
.rem,
diff --git a/src/value.zig b/src/value.zig
index 1de0f66717cf..b32eefb7995f 100644
--- a/src/value.zig
+++ b/src/value.zig
@@ -2605,6 +2605,48 @@ pub const Value = struct {
return mod.intValue_big(ty, result_q.toConst());
}
+ pub fn intDivCeil(lhs: Value, rhs: Value, ty: Type, allocator: Allocator, mod: *Module) !Value {
+ if (ty.zigTypeTag(mod) == .Vector) {
+ const result_data = try allocator.alloc(InternPool.Index, ty.vectorLen(mod));
+ const scalar_ty = ty.scalarType(mod);
+ for (result_data, 0..) |*scalar, i| {
+ const lhs_elem = try lhs.elemValue(mod, i);
+ const rhs_elem = try rhs.elemValue(mod, i);
+ scalar.* = try (try intDivCeilScalar(lhs_elem, rhs_elem, scalar_ty, allocator, mod)).intern(scalar_ty, mod);
+ }
+ return Value.fromInterned((try mod.intern(.{ .aggregate = .{
+ .ty = ty.toIntern(),
+ .storage = .{ .elems = result_data },
+ } })));
+ }
+ return intDivCeilScalar(lhs, rhs, ty, allocator, mod);
+ }
+
+ pub fn intDivCeilScalar(lhs: Value, rhs: Value, ty: Type, allocator: Allocator, mod: *Module) !Value {
+ // TODO is this a performance issue? maybe we should try the operation without
+ // resorting to BigInt first.
+ var lhs_space: Value.BigIntSpace = undefined;
+ var rhs_space: Value.BigIntSpace = undefined;
+ const lhs_bigint = lhs.toBigInt(&lhs_space, mod);
+ const rhs_bigint = rhs.toBigInt(&rhs_space, mod);
+ const limbs_q = try allocator.alloc(
+ std.math.big.Limb,
+ lhs_bigint.limbs.len,
+ );
+ const limbs_r = try allocator.alloc(
+ std.math.big.Limb,
+ rhs_bigint.limbs.len,
+ );
+ const limbs_buffer = try allocator.alloc(
+ std.math.big.Limb,
+ std.math.big.int.calcDivLimbsBufferLen(lhs_bigint.limbs.len, rhs_bigint.limbs.len),
+ );
+ var result_q = BigIntMutable{ .limbs = limbs_q, .positive = undefined, .len = undefined };
+ var result_r = BigIntMutable{ .limbs = limbs_r, .positive = undefined, .len = undefined };
+ result_q.divCeil(&result_r, lhs_bigint, rhs_bigint, limbs_buffer);
+ return mod.intValue_big(ty, result_q.toConst());
+ }
+
pub fn intMod(lhs: Value, rhs: Value, ty: Type, allocator: Allocator, mod: *Module) !Value {
if (ty.zigTypeTag(mod) == .Vector) {
const result_data = try allocator.alloc(InternPool.Index, ty.vectorLen(mod));
@@ -3336,6 +3378,51 @@ pub const Value = struct {
} })));
}
+ pub fn floatDivCeil(
+ lhs: Value,
+ rhs: Value,
+ float_type: Type,
+ arena: Allocator,
+ mod: *Module,
+ ) !Value {
+ if (float_type.zigTypeTag(mod) == .Vector) {
+ const result_data = try arena.alloc(InternPool.Index, float_type.vectorLen(mod));
+ const scalar_ty = float_type.scalarType(mod);
+ for (result_data, 0..) |*scalar, i| {
+ const lhs_elem = try lhs.elemValue(mod, i);
+ const rhs_elem = try rhs.elemValue(mod, i);
+ scalar.* = try (try floatDivCeilScalar(lhs_elem, rhs_elem, scalar_ty, mod)).intern(scalar_ty, mod);
+ }
+ return Value.fromInterned((try mod.intern(.{ .aggregate = .{
+ .ty = float_type.toIntern(),
+ .storage = .{ .elems = result_data },
+ } })));
+ }
+ return floatDivCeilScalar(lhs, rhs, float_type, mod);
+ }
+
+ pub fn floatDivCeilScalar(
+ lhs: Value,
+ rhs: Value,
+ float_type: Type,
+ mod: *Module,
+ ) !Value {
+ const target = mod.getTarget();
+ // TODO: Replace @ceil(x / y) with @divCeil(x, y).
+ const storage: InternPool.Key.Float.Storage = switch (float_type.floatBits(target)) {
+ 16 => .{ .f16 = @ceil(lhs.toFloat(f16, mod) / rhs.toFloat(f16, mod)) },
+ 32 => .{ .f32 = @ceil(lhs.toFloat(f32, mod) / rhs.toFloat(f32, mod)) },
+ 64 => .{ .f64 = @ceil(lhs.toFloat(f64, mod) / rhs.toFloat(f64, mod)) },
+ 80 => .{ .f80 = @ceil(lhs.toFloat(f80, mod) / rhs.toFloat(f80, mod)) },
+ 128 => .{ .f128 = @ceil(lhs.toFloat(f128, mod) / rhs.toFloat(f128, mod)) },
+ else => unreachable,
+ };
+ return Value.fromInterned((try mod.intern(.{ .float = .{
+ .ty = float_type.toIntern(),
+ .storage = storage,
+ } })));
+ }
+
pub fn floatDivTrunc(
lhs: Value,
rhs: Value,
diff --git a/test/behavior/int128.zig b/test/behavior/int128.zig
index 6d7b54ea314d..0c58e2189132 100644
--- a/test/behavior/int128.zig
+++ b/test/behavior/int128.zig
@@ -61,6 +61,7 @@ test "int128" {
const a: i128 = -170141183460469231731687303715884105728;
const b: i128 = -0x8000_0000_0000_0000_0000_0000_0000_0000;
try expect(@divFloor(b, 1_000_000) == -170141183460469231731687303715885);
+ try expect(@divCeil(b, 1_000_000) == -170141183460469231731687303715884);
try expect(a == b);
}
diff --git a/test/behavior/int_div.zig b/test/behavior/int_div.zig
index bc570434cec9..ea381f6798b0 100644
--- a/test/behavior/int_div.zig
+++ b/test/behavior/int_div.zig
@@ -30,6 +30,16 @@ fn testDivision() !void {
try expect(divFloor(i32, -14, 12) == -2);
try expect(divFloor(i32, -2, 12) == -1);
+ try expect(divCeil(i8, 5, 3) == 2);
+ try expect(divCeil(i16, -5, 3) == -1);
+ try expect(divCeil(i64, -0x80000000, -2) == 0x40000000);
+ try expect(divCeil(i32, 0, -0x80000000) == 0);
+ try expect(divCeil(i64, -0x40000001, 0x40000000) == -1);
+ try expect(divCeil(i32, -0x80000000, 1) == -0x80000000);
+ try expect(divCeil(i32, 10, 12) == 1);
+ try expect(divCeil(i32, -14, 12) == -1);
+ try expect(divCeil(i32, -2, 12) == 0);
+
try expect(divTrunc(i32, 5, 3) == 1);
try expect(divTrunc(i32, -5, 3) == -1);
try expect(divTrunc(i32, 9, -10) == 0);
@@ -58,6 +68,24 @@ fn testDivision() !void {
try expect(
1194735857077236777412821811143690633098347576 / 508740759824825164163191790951174292733114988 == 2,
);
+ try expect(
+ @divFloor(-1194735857077236777412821811143690633098347576, 508740759824825164163191790951174292733114988) == -3,
+ );
+ try expect(
+ @divFloor(1194735857077236777412821811143690633098347576, -508740759824825164163191790951174292733114988) == -3,
+ );
+ try expect(
+ @divFloor(-1194735857077236777412821811143690633098347576, -508740759824825164163191790951174292733114988) == 2,
+ );
+ try expect(
+ @divCeil(-1194735857077236777412821811143690633098347576, 508740759824825164163191790951174292733114988) == -2,
+ );
+ try expect(
+ @divCeil(1194735857077236777412821811143690633098347576, -508740759824825164163191790951174292733114988) == -2,
+ );
+ try expect(
+ @divCeil(-1194735857077236777412821811143690633098347576, -508740759824825164163191790951174292733114988) == 3,
+ );
try expect(
@divTrunc(-1194735857077236777412821811143690633098347576, 508740759824825164163191790951174292733114988) == -2,
);
@@ -81,6 +109,9 @@ fn divExact(comptime T: type, a: T, b: T) T {
fn divFloor(comptime T: type, a: T, b: T) T {
return @divFloor(a, b);
}
+fn divCeil(comptime T: type, a: T, b: T) T {
+ return @divCeil(a, b);
+}
fn divTrunc(comptime T: type, a: T, b: T) T {
return @divTrunc(a, b);
}
diff --git a/test/behavior/math.zig b/test/behavior/math.zig
index 5b5d62df96bb..51a2f02cebd7 100644
--- a/test/behavior/math.zig
+++ b/test/behavior/math.zig
@@ -535,6 +535,8 @@ fn testDivisionFP16() !void {
try expect(divFloor(f16, 5.0, 3.0) == 1.0);
try expect(divFloor(f16, -5.0, 3.0) == -2.0);
+ try expect(divCeil(f16, 5.0, 3.0) == 2.0);
+ try expect(divCeil(f16, -5.0, 3.0) == -1.0);
try expect(divTrunc(f16, 5.0, 3.0) == 1.0);
try expect(divTrunc(f16, -5.0, 3.0) == -1.0);
try expect(divTrunc(f16, 9.0, -10.0) == 0.0);
@@ -550,6 +552,9 @@ fn divExact(comptime T: type, a: T, b: T) T {
fn divFloor(comptime T: type, a: T, b: T) T {
return @divFloor(a, b);
}
+fn divCeil(comptime T: type, a: T, b: T) T {
+ return @divCeil(a, b);
+}
fn divTrunc(comptime T: type, a: T, b: T) T {
return @divTrunc(a, b);
}
diff --git a/test/behavior/stage2_wasm_div.zig b/test/behavior/stage2_wasm_div.zig
index ed09578fabb5..92aee63b94a3 100644
--- a/test/behavior/stage2_wasm_div.zig
+++ b/test/behavior/stage2_wasm_div.zig
@@ -3,7 +3,8 @@ const builtin = @import("builtin");
const expect = std.testing.expect;
test "wasm integer division" {
- // This test is copied from int_div.zig, with additional test cases for @divFloor on floats.
+ // This test is copied from int_div.zig, with additional test cases for @divFloor and @divCeil
+ // on floats.
// TODO: Remove this test once the division tests in math.zig and int_div.zig pass with the
// stage2 wasm backend.
if (builtin.zig_backend != .stage2_wasm) return error.SkipZigTest;
@@ -30,6 +31,20 @@ fn testDivision() !void {
try expect(divFloor(f16, -43.0, 12.0) == -4.0);
try expect(divFloor(f64, -90.0, -9.0) == 10.0);
+ try expect(divCeil(i8, 5, 3) == 2);
+ try expect(divCeil(i16, -5, 3) == -1);
+ try expect(divCeil(i64, -0x80000000, -2) == 0x40000000);
+ try expect(divCeil(i32, 0, -0x80000000) == 0);
+ try expect(divCeil(i64, -0x40000001, 0x40000000) == -1);
+ try expect(divCeil(i32, -0x80000000, 1) == -0x80000000);
+ try expect(divCeil(i32, 10, 12) == 1);
+ try expect(divCeil(i32, -14, 12) == -1);
+ try expect(divCeil(i32, -2, 12) == 0);
+ try expect(divCeil(f32, 56.0, 9.0) == 7.0);
+ try expect(divCeil(f32, 1053.0, -41.0) == -25.0);
+ try expect(divCeil(f16, -43.0, 12.0) == -3.0);
+ try expect(divCeil(f64, -90.0, -9.0) == 10.0);
+
try expect(mod(u32, 10, 12) == 10);
try expect(mod(i32, 10, 12) == 10);
try expect(mod(i64, -14, 12) == 10);
@@ -46,6 +61,9 @@ fn div(comptime T: type, a: T, b: T) T {
fn divFloor(comptime T: type, a: T, b: T) T {
return @divFloor(a, b);
}
+fn divCeil(comptime T: type, a: T, b: T) T {
+ return @divCeil(a, b);
+}
fn mod(comptime T: type, a: T, b: T) T {
return @mod(a, b);
}
diff --git a/test/behavior/vector.zig b/test/behavior/vector.zig
index 6a38891494c5..e00d727a9bb3 100644
--- a/test/behavior/vector.zig
+++ b/test/behavior/vector.zig
@@ -559,8 +559,12 @@ test "vector division operators" {
for (@as([4]T, d2), 0..) |v, i| {
try expect(@divFloor(x[i], y[i]) == v);
}
- const d3 = @divTrunc(x, y);
+ const d3 = @divCeil(x, y);
for (@as([4]T, d3), 0..) |v, i| {
+ try expect(@divCeil(x[i], y[i]) == v);
+ }
+ const d4 = @divTrunc(x, y);
+ for (@as([4]T, d4), 0..) |v, i| {
try expect(@divTrunc(x[i], y[i]) == v);
}
}
@@ -1271,11 +1275,13 @@ test "zero divisor" {
const v2 = @divExact(zeros, ones);
const v3 = @divTrunc(zeros, ones);
const v4 = @divFloor(zeros, ones);
+ const v5 = @divCeil(zeros, ones);
_ = v1[0];
_ = v2[0];
_ = v3[0];
_ = v4[0];
+ _ = v5[0];
}
test "zero multiplicand" {
diff --git a/test/cases/compile_errors/signed_integer_division.zig b/test/cases/compile_errors/signed_integer_division.zig
index 7e968ac77eb1..691476623931 100644
--- a/test/cases/compile_errors/signed_integer_division.zig
+++ b/test/cases/compile_errors/signed_integer_division.zig
@@ -6,4 +6,4 @@ export fn foo(a: i32, b: i32) i32 {
// backend=stage2
// target=native
//
-// :2:14: error: division with 'i32' and 'i32': signed integers must use @divTrunc, @divFloor, or @divExact
+// :2:14: error: division with 'i32' and 'i32': signed integers must use @divTrunc, @divFloor, @divCeil, or @divExact