authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2021-10-03 01:20:10+02:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2021-10-04 11:25:29+02:00
log5907b3e3830f95d111d9d60027af1350b81f4378
tree6b9c25e6521eec9c107136a3785b849586da89df
parent15351f206fba9f7b2bba2bfce90cb6359846ce3c

big ints: Improve karatsuba multiplication


1 files changed, 211 insertions(+), 85 deletions(-)

lib/std/math/big/int.zig+211-85
...@@ -86,6 +86,24 @@ pub fn addMulLimbWithCarry(a: Limb, b: Limb, c: Limb, carry: *Limb) Limb {...@@ -86,6 +86,24 @@ pub fn addMulLimbWithCarry(a: Limb, b: Limb, c: Limb, carry: *Limb) Limb {
86 return r1;86 return r1;
87}87}
8888
89/// a - b * c - *carry, sets carry to the overflow bits
90fn subMulLimbWithBorrow(a: Limb, b: Limb, c: Limb, carry: *Limb) Limb {
91 // r1 = a - *carry
92 var r1: Limb = undefined;
93 const c1: Limb = @boolToInt(@subWithOverflow(Limb, a, carry.*, &r1));
94
95 // r2 = b * c
96 const bc = @as(DoubleLimb, std.math.mulWide(Limb, b, c));
97 const r2 = @truncate(Limb, bc);
98 const c2 = @truncate(Limb, bc >> limb_bits);
99
100 // r1 = r1 - r2
101 const c3: Limb = @boolToInt(@subWithOverflow(Limb, r1, r2, &r1));
102 carry.* = c1 + c2 + c3;
103
104 return r1;
105}
106
89/// Used to indicate either limit of a 2s-complement integer.107/// Used to indicate either limit of a 2s-complement integer.
90pub const TwosCompIntLimit = enum {108pub const TwosCompIntLimit = enum {
91 // The low limit, either 0x00 (unsigned) or (-)0x80 (signed) for an 8-bit integer.109 // The low limit, either 0x00 (unsigned) or (-)0x80 (signed) for an 8-bit integer.
...@@ -640,7 +658,7 @@ pub const Mutable = struct {...@@ -640,7 +658,7 @@ pub const Mutable = struct {
640658
641 mem.set(Limb, rma.limbs[0 .. a.limbs.len + b.limbs.len + 1], 0);659 mem.set(Limb, rma.limbs[0 .. a.limbs.len + b.limbs.len + 1], 0);
642660
643 llmulacc(allocator, rma.limbs, a.limbs, b.limbs);661 llmulacc(.add, allocator, rma.limbs, a.limbs, b.limbs);
644662
645 rma.normalize(a.limbs.len + b.limbs.len);663 rma.normalize(a.limbs.len + b.limbs.len);
646 rma.positive = (a.positive == b.positive);664 rma.positive = (a.positive == b.positive);
...@@ -665,9 +683,9 @@ pub const Mutable = struct {...@@ -665,9 +683,9 @@ pub const Mutable = struct {
665 mem.set(Limb, rma.limbs[0..req_limbs], 0);683 mem.set(Limb, rma.limbs[0..req_limbs], 0);
666684
667 if (a_limbs.len >= b_limbs.len) {685 if (a_limbs.len >= b_limbs.len) {
668 llmulacc_lo(rma.limbs, a_limbs, b_limbs);686 llmulaccLow(rma.limbs, a_limbs, b_limbs);
669 } else {687 } else {
670 llmulacc_lo(rma.limbs, b_limbs, a_limbs);688 llmulaccLow(rma.limbs, b_limbs, a_limbs);
671 }689 }
672690
673 rma.normalize(math.min(req_limbs, a.limbs.len + b.limbs.len));691 rma.normalize(math.min(req_limbs, a.limbs.len + b.limbs.len));
...@@ -691,7 +709,7 @@ pub const Mutable = struct {...@@ -691,7 +709,7 @@ pub const Mutable = struct {
691709
692 mem.set(Limb, rma.limbs, 0);710 mem.set(Limb, rma.limbs, 0);
693711
694 llsquare_basecase(rma.limbs, a.limbs);712 llsquareBasecase(rma.limbs, a.limbs);
695713
696 rma.normalize(2 * a.limbs.len + 1);714 rma.normalize(2 * a.limbs.len + 1);
697 rma.positive = true;715 rma.positive = true;
...@@ -1219,7 +1237,7 @@ pub const Mutable = struct {...@@ -1219,7 +1237,7 @@ pub const Mutable = struct {
1219 /// Asserts `r` has enough storage to store the result.1237 /// Asserts `r` has enough storage to store the result.
1220 /// The upper bound is `calcTwosCompLimbCount(a.len)`.1238 /// The upper bound is `calcTwosCompLimbCount(a.len)`.
1221 pub fn truncate(r: *Mutable, a: Const, signedness: std.builtin.Signedness, bit_count: usize) void {1239 pub fn truncate(r: *Mutable, a: Const, signedness: std.builtin.Signedness, bit_count: usize) void {
1222 const req_limbs = (bit_count + @bitSizeOf(Limb) - 1) / @bitSizeOf(Limb);1240 const req_limbs = calcTwosCompLimbCount(bit_count);
12231241
1224 // Handle 0-bit integers.1242 // Handle 0-bit integers.
1225 if (req_limbs == 0 or a.eqZero()) {1243 if (req_limbs == 0 or a.eqZero()) {
...@@ -2319,8 +2337,8 @@ pub const Managed = struct {...@@ -2319,8 +2337,8 @@ pub const Managed = struct {
2319 }2337 }
2320};2338};
23212339
2322/// r = a * b, ignoring overflow2340/// r = r + a * b, ignoring overflow
2323fn llmulacc_lo(r: []Limb, a: []const Limb, b: []const Limb) void {2341fn llmulaccLow(r: []Limb, a: []const Limb, b: []const Limb) void {
2324 assert(r.len >= a.len);2342 assert(r.len >= a.len);
2325 assert(a.len >= b.len);2343 assert(a.len >= b.len);
23262344
...@@ -2328,32 +2346,41 @@ fn llmulacc_lo(r: []Limb, a: []const Limb, b: []const Limb) void {...@@ -2328,32 +2346,41 @@ fn llmulacc_lo(r: []Limb, a: []const Limb, b: []const Limb) void {
23282346
2329 var i: usize = 0;2347 var i: usize = 0;
2330 while (i < b.len) : (i += 1) {2348 while (i < b.len) : (i += 1) {
2331 llmulDigit(r[i..], a, b[i]);2349 llmulLimb(.add, r[i..], a, b[i]);
2332 }2350 }
2333}2351}
23342352
2353/// Different operators which can be used in accumulation style functions
2354/// (llmulacc, llmulaccKaratsuba, llmulaccLong, llmulLimb). In all these functions,
2355/// a computed value is accumulated with an existing result.
2356const AccOp = enum {
2357 /// The computed value is added to the result.
2358 add,
2359
2360 /// The computed value is subtracted from the result.
2361 sub,
2362};
2363
2335/// Knuth 4.3.1, Algorithm M.2364/// Knuth 4.3.1, Algorithm M.
2336///2365///
2366/// r = r (op) a * b
2337/// r MUST NOT alias any of a or b.2367/// r MUST NOT alias any of a or b.
2338fn llmulacc(opt_allocator: ?*Allocator, r: []Limb, a: []const Limb, b: []const Limb) void {2368fn llmulacc(comptime op: AccOp, opt_allocator: ?*Allocator, r: []Limb, a: []const Limb, b: []const Limb) void {
2339 @setRuntimeSafety(debug_safety);2369 @setRuntimeSafety(debug_safety);
2370 assert(r.len >= a.len + b.len);
23402371
2341 const a_norm = a[0..llnormalize(a)];2372 // Order greatest first.
2342 const b_norm = b[0..llnormalize(b)];2373 var x = a;
2343 var x = a_norm;2374 var y = b;
2344 var y = b_norm;2375 if (a.len < b.len) {
2345 if (a_norm.len > b_norm.len) {2376 x = b;
2346 x = b_norm;2377 y = a;
2347 y = a_norm;
2348 }2378 }
23492379
2350 assert(r.len >= x.len + y.len + 1);
2351
2352 // 48 is a pretty abitrary size chosen based on performance of a factorial program.
2353 k_mul: {2380 k_mul: {
2354 if (x.len > 48) {2381 if (y.len > 48) {
2355 if (opt_allocator) |allocator| {2382 if (opt_allocator) |allocator| {
2356 llmulacc_karatsuba(allocator, r, x, y) catch |err| switch (err) {2383 llmulaccKaratsuba(op, allocator, r, x, y) catch |err| switch (err) {
2357 error.OutOfMemory => break :k_mul, // handled below2384 error.OutOfMemory => break :k_mul, // handled below
2358 };2385 };
2359 return;2386 return;
...@@ -2361,83 +2388,153 @@ fn llmulacc(opt_allocator: ?*Allocator, r: []Limb, a: []const Limb, b: []const L...@@ -2361,83 +2388,153 @@ fn llmulacc(opt_allocator: ?*Allocator, r: []Limb, a: []const Limb, b: []const L
2361 }2388 }
2362 }2389 }
23632390
2364 // Basecase multiplication2391 llmulaccLong(op, r, x, y);
2365 var i: usize = 0;
2366 while (i < x.len) : (i += 1) {
2367 llmulDigit(r[i..], y, x[i]);
2368 }
2369}2392}
23702393
2371/// Knuth 4.3.1, Algorithm M.2394/// Knuth 4.3.1, Algorithm M.
2372///2395///
2396/// r = r (op) a * b
2373/// r MUST NOT alias any of a or b.2397/// r MUST NOT alias any of a or b.
2374fn llmulacc_karatsuba(allocator: *Allocator, r: []Limb, x: []const Limb, y: []const Limb) error{OutOfMemory}!void {2398fn llmulaccKaratsuba(
2399 comptime op: AccOp,
2400 allocator: *Allocator,
2401 r: []Limb,
2402 a: []const Limb,
2403 b: []const Limb,
2404) error{OutOfMemory}!void {
2375 @setRuntimeSafety(debug_safety);2405 @setRuntimeSafety(debug_safety);
2406 assert(r.len >= a.len + b.len);
2407 assert(a.len >= b.len);
23762408
2377 assert(r.len >= x.len + y.len + 1);2409 // Classical karatsuba algorithm:
23782410 // a = a1 * B + a0
2379 const split = @divFloor(x.len, 2);2411 // b = b1 * B + b0
2380 var x0 = x[0..split];2412 // Where a0, b0 < B
2381 var x1 = x[split..x.len];2413 //
2382 var y0 = y[0..split];2414 // We then have:
2383 var y1 = y[split..y.len];2415 // ab = a * b
23842416 // = (a1 * B + a0) * (b1 * B + b0)
2385 var tmp = try allocator.alloc(Limb, x1.len + y1.len + 1);2417 // = a1 * b1 * B * B + a1 * B * b0 + a0 * b1 * B + a0 * b0
2418 // = a1 * b1 * B * B + (a1 * b0 + a0 * b1) * B + a0 * b0
2419 //
2420 // Note that:
2421 // a1 * b0 + a0 * b1
2422 // = (a1 + a0)(b1 + b0) - a1 * b1 - a0 * b0
2423 // = (a0 - a1)(b1 - b0) + a1 * b1 + a0 * b0
2424 //
2425 // This yields:
2426 // ab = p2 * B^2 + (p0 + p1 + p2) * B + p0
2427 //
2428 // Where:
2429 // p0 = a0 * b0
2430 // p1 = (a0 - a1)(b1 - b0)
2431 // p2 = a1 * b1
2432 //
2433 // Note, (a0 - a1) and (b1 - b0) produce values -B < x < B, and so we need to mind the sign here.
2434 // We also have:
2435 // 0 <= p0 <= 2B
2436 // -2B <= p1 <= 2B
2437 //
2438 // Note, when B is a multiple of the limb size, multiplies by B amount to shifts or
2439 // slices of a limbs array.
2440
2441 const split = b.len / 2; // B
2442 const a0 = a[0..llnormalize(a[0..split])];
2443 const a1 = a[split..][0..llnormalize(a[split..])];
2444 const b0 = b[0..llnormalize(b[0..split])];
2445 const b1 = b[split..][0..llnormalize(b[split..])];
2446
2447 // Note that the above slices work because we have a.len > b.len.
2448 // We now also have:
2449 // a1.len >= a0.len
2450 // a1.len >= b1.len >= b0.len
2451 // a0.len == b0.len
2452
2453 // We need some temporary memory to store intermediate results.
2454 // Note, we can reduce the amount of temporaries we need by reordering the computation here:
2455 // ab = p2 * B^2 + (p0 + p1 + p2) * B + p0
2456 // = p2 * B^2 + (p0 * B + p1 * B + p2 * B) + p0
2457 // = (p2 * B^2 + p2 * B) + (p0 * B + p0) + p1 * B
2458 // By allocating a1.len * b1.len we can be sure that all the intermediary results fit.
2459 const tmp = try allocator.alloc(Limb, a.len - split + b.len - split);
2386 defer allocator.free(tmp);2460 defer allocator.free(tmp);
2387 mem.set(Limb, tmp, 0);
23882461
2389 llmulacc(allocator, tmp, x1, y1);2462 // Compute p2.
2463 mem.set(Limb, tmp, 0);
2464 llmulacc(.add, allocator, tmp, a1, b1);
2465 const p2 = tmp[0 .. llnormalize(tmp)];
23902466
2391 var length = llnormalize(tmp);2467 // Add terms p2 * B^2 and p2 * B to the result.
2392 _ = llaccum(r[split..], tmp[0..length]);2468 _ = llaccum(op, r[split..], p2);
2393 _ = llaccum(r[split * 2 ..], tmp[0..length]);2469 _ = llaccum(op, r[split * 2..], p2);
23942470
2395 mem.set(Limb, tmp[0..length], 0);2471 // Compute p0.
2472 mem.set(Limb, p2, 0);
2473 llmulacc(.add, allocator, tmp, a0, b0);
2474 const p0 = tmp[0 .. llnormalize(tmp[0..a0.len + b0.len])];
23962475
2397 llmulacc(allocator, tmp, x0, y0);2476 // Add terms p0 * B and p0 to the result.
2477 _ = llaccum(op, r, p0);
2478 _ = llaccum(op, r[split..], p0);
23982479
2399 length = llnormalize(tmp);2480 // Finally, compute and add p1.
2400 _ = llaccum(r[0..], tmp[0..length]);2481 const j0_sign = llcmp(a0, a1);
2401 _ = llaccum(r[split..], tmp[0..length]);2482 const j1_sign = llcmp(b1, b0);
24022483
2403 const x_cmp = llcmp(x1, x0);2484 if (j0_sign * j1_sign == 0) {
2404 const y_cmp = llcmp(y1, y0);2485 // p1 is zero, we don't need to do any computation at all.
2405 if (x_cmp * y_cmp == 0) {
2406 return;2486 return;
2407 }2487 }
2408 const x0_len = llnormalize(x0);2488
2409 const x1_len = llnormalize(x1);2489 mem.set(Limb, tmp, 0);
2410 var j0 = try allocator.alloc(Limb, math.max(x0_len, x1_len));2490
2411 defer allocator.free(j0);2491 // p1 is nonzero, so compute the intermediary terms j0 = a0 - a1 and j1 = b1 - b0.
2412 if (x_cmp == 1) {2492 // Note that in this case, we again need some storage for intermediary results
2413 llsub(j0, x1[0..x1_len], x0[0..x0_len]);2493 // j0 and j1. Since we have tmp.len >= 2B, we can store both
2494 // intermediaries in the already allocated array.
2495 const j0 = tmp[0..a1.len];
2496 const j1 = tmp[a1.len..];
2497
2498 // Ensure that no subtraction overflows.
2499 if (j0_sign == 1) {
2500 // a0 > a1.
2501 _ = llsubcarry(j0, a0, a1);
2414 } else {2502 } else {
2415 llsub(j0, x0[0..x0_len], x1[0..x1_len]);2503 // a0 < a1.
2504 _ = llsubcarry(j0, a1, a0);
2416 }2505 }
24172506
2418 const y0_len = llnormalize(y0);2507 if (j1_sign == 1) {
2419 const y1_len = llnormalize(y1);2508 // b1 > b0.
2420 var j1 = try allocator.alloc(Limb, math.max(y0_len, y1_len));2509 _ = llsubcarry(j1, b1, b0);
2421 defer allocator.free(j1);
2422 if (y_cmp == 1) {
2423 llsub(j1, y1[0..y1_len], y0[0..y0_len]);
2424 } else {2510 } else {
2425 llsub(j1, y0[0..y0_len], y1[0..y1_len]);2511 // b1 > b0.
2512 _ = llsubcarry(j1, b0, b1);
2426 }2513 }
2427 if (x_cmp == y_cmp) {
2428 mem.set(Limb, tmp[0..length], 0);
2429 llmulacc(allocator, tmp, j0, j1);
24302514
2431 length = llnormalize(tmp);2515 if (j0_sign * j1_sign == 1) {
2432 llsub(r[split..], r[split..], tmp[0..length]);2516 // If j0 and j1 are both positive, we now have:
2517 // p1 = j0 * j1
2518 // If j0 and j1 are both negative, we now have:
2519 // p1 = -j0 * -j1 = j0 * j1
2520 // In this case we can add p1 to the result using llmulacc.
2521 llmulacc(op, allocator, r[split..], j0[0..llnormalize(j0)], j1[0..llnormalize(j1)]);
2433 } else {2522 } else {
2434 llmulacc(allocator, r[split..], j0, j1);2523 // In this case either j0 or j1 is negative, an we have:
2524 // p1 = -(j0 * j1)
2525 // Now we need to subtract instead of accumulate.
2526 const inverted_op = if (op == .add) .sub else .add;
2527 llmulacc(inverted_op, allocator, r[split..], j0[0..llnormalize(j0)], j1[0..llnormalize(j1)]);
2435 }2528 }
2436}2529}
24372530
2438// r = r + a2531// r = r (op) a
2439fn llaccum(r: []Limb, a: []const Limb) Limb {2532fn llaccum(comptime op: AccOp, r: []Limb, a: []const Limb) Limb {
2440 @setRuntimeSafety(debug_safety);2533 @setRuntimeSafety(debug_safety);
2534 if (op == .sub) {
2535 return llsubcarry(r, r, a);
2536 }
2537
2441 assert(r.len != 0 and a.len != 0);2538 assert(r.len != 0 and a.len != 0);
2442 assert(r.len >= a.len);2539 assert(r.len >= a.len);
24432540
...@@ -2486,24 +2583,53 @@ pub fn llcmp(a: []const Limb, b: []const Limb) i8 {...@@ -2486,24 +2583,53 @@ pub fn llcmp(a: []const Limb, b: []const Limb) i8 {
2486 }2583 }
2487}2584}
24882585
2489fn llmulDigit(acc: []Limb, y: []const Limb, xi: Limb) void {2586// r = r (op) y * xi
2587fn llmulaccLong(comptime op: AccOp, r: []Limb, a: []const Limb, b: []const Limb) void {
2588 @setRuntimeSafety(debug_safety);
2589 assert(r.len >= a.len + b.len);
2590 assert(a.len >= b.len);
2591
2592 var i: usize = 0;
2593 while (i < a.len) : (i += 1) {
2594 llmulLimb(op, r[i..], b, a[i]);
2595 }
2596}
2597
2598// r = r (op) y * xi
2599fn llmulLimb(comptime op: AccOp, acc: []Limb, y: []const Limb, xi: Limb) void {
2490 @setRuntimeSafety(debug_safety);2600 @setRuntimeSafety(debug_safety);
2491 if (xi == 0) {2601 if (xi == 0) {
2492 return;2602 return;
2493 }2603 }
24942604
2495 var carry: Limb = 0;
2496 var a_lo = acc[0..y.len];2605 var a_lo = acc[0..y.len];
2497 var a_hi = acc[y.len..];2606 var a_hi = acc[y.len..];
24982607
2499 var j: usize = 0;2608 switch (op) {
2500 while (j < a_lo.len) : (j += 1) {2609 .add => {
2501 a_lo[j] = @call(.{ .modifier = .always_inline }, addMulLimbWithCarry, .{ a_lo[j], y[j], xi, &carry });2610 var carry: Limb = 0;
2502 }2611 var j: usize = 0;
2612 while (j < a_lo.len) : (j += 1) {
2613 a_lo[j] = addMulLimbWithCarry(a_lo[j], y[j], xi, &carry);
2614 }
25032615
2504 j = 0;2616 j = 0;
2505 while ((carry != 0) and (j < a_hi.len)) : (j += 1) {2617 while ((carry != 0) and (j < a_hi.len)) : (j += 1) {
2506 carry = @boolToInt(@addWithOverflow(Limb, a_hi[j], carry, &a_hi[j]));2618 carry = @boolToInt(@addWithOverflow(Limb, a_hi[j], carry, &a_hi[j]));
2619 }
2620 },
2621 .sub => {
2622 var borrow: Limb = 0;
2623 var j: usize = 0;
2624 while (j < a_lo.len) : (j += 1) {
2625 a_lo[j] = subMulLimbWithBorrow(a_lo[j], y[j], xi, &borrow);
2626 }
2627
2628 j = 0;
2629 while ((borrow != 0) and (j < a_hi.len)) : (j += 1) {
2630 borrow = @boolToInt(@subWithOverflow(Limb, a_hi[j], borrow, &a_hi[j]));
2631 }
2632 },
2507 }2633 }
2508}2634}
25092635
...@@ -2964,7 +3090,7 @@ fn llsignedxor(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_...@@ -2964,7 +3090,7 @@ fn llsignedxor(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_
2964}3090}
29653091
2966/// r MUST NOT alias x.3092/// r MUST NOT alias x.
2967fn llsquare_basecase(r: []Limb, x: []const Limb) void {3093fn llsquareBasecase(r: []Limb, x: []const Limb) void {
2968 @setRuntimeSafety(debug_safety);3094 @setRuntimeSafety(debug_safety);
29693095
2970 const x_norm = x;3096 const x_norm = x;
...@@ -2987,7 +3113,7 @@ fn llsquare_basecase(r: []Limb, x: []const Limb) void {...@@ -2987,7 +3113,7 @@ fn llsquare_basecase(r: []Limb, x: []const Limb) void {
29873113
2988 for (x_norm) |v, i| {3114 for (x_norm) |v, i| {
2989 // Accumulate all the x[i]*x[j] (with x!=j) products3115 // Accumulate all the x[i]*x[j] (with x!=j) products
2990 llmulDigit(r[2 * i + 1 ..], x_norm[i + 1 ..], v);3116 llmulLimb(.add, r[2 * i + 1 ..], x_norm[i + 1 ..], v);
2991 }3117 }
29923118
2993 // Each product appears twice, multiply by 23119 // Each product appears twice, multiply by 2
...@@ -2995,7 +3121,7 @@ fn llsquare_basecase(r: []Limb, x: []const Limb) void {...@@ -2995,7 +3121,7 @@ fn llsquare_basecase(r: []Limb, x: []const Limb) void {
29953121
2996 for (x_norm) |v, i| {3122 for (x_norm) |v, i| {
2997 // Compute and add the squares3123 // Compute and add the squares
2998 llmulDigit(r[2 * i ..], x[i .. i + 1], v);3124 llmulLimb(.add, r[2 * i ..], x[i .. i + 1], v);
2999 }3125 }
3000}3126}
30013127
...@@ -3034,12 +3160,12 @@ fn llpow(r: []Limb, a: []const Limb, b: u32, tmp_limbs: []Limb) void {...@@ -3034,12 +3160,12 @@ fn llpow(r: []Limb, a: []const Limb, b: u32, tmp_limbs: []Limb) void {
3034 while (i < exp_bits) : (i += 1) {3160 while (i < exp_bits) : (i += 1) {
3035 // Square3161 // Square
3036 mem.set(Limb, tmp2, 0);3162 mem.set(Limb, tmp2, 0);
3037 llsquare_basecase(tmp2, tmp1[0..llnormalize(tmp1)]);3163 llsquareBasecase(tmp2, tmp1[0..llnormalize(tmp1)]);
3038 mem.swap([]Limb, &tmp1, &tmp2);3164 mem.swap([]Limb, &tmp1, &tmp2);
3039 // Multiply by a3165 // Multiply by a
3040 if (@shlWithOverflow(u32, exp, 1, &exp)) {3166 if (@shlWithOverflow(u32, exp, 1, &exp)) {
3041 mem.set(Limb, tmp2, 0);3167 mem.set(Limb, tmp2, 0);
3042 llmulacc(null, tmp2, tmp1[0..llnormalize(tmp1)], a);3168 llmulacc(.add, null, tmp2, tmp1[0..llnormalize(tmp1)], a);
3043 mem.swap([]Limb, &tmp1, &tmp2);3169 mem.swap([]Limb, &tmp1, &tmp2);
3044 }3170 }
3045 }3171 }