diff --git a/bend2/comp.ts b/bend2/comp.ts index 5a24c7b1b3..f73b4ce4a6 100644 --- a/bend2/comp.ts +++ b/bend2/comp.ts @@ -180,6 +180,7 @@ const OPERATIONS: Record = Object.setPrototypeOf({ u32_mul: { C: "U32_BIN($0, *, $1)", JS: "(Math.imul($0, $1) >>> 0)" }, // C only: a JS file runs Base's U32.pow, as intr_of skips an op with no JS u32_pow: { C: "u32_pow($0, $1)" }, + u32_log2: { C: "(31 - CLZ((u32)$0 | 1))" }, u32_div: { C: "((u32)($1) == 0 ? 0 : (u64)U32_QUO((u32)($0), (u32)($1)))", JS: "($1 === 0 ? 0 : ($0 / $1) >>> 0)", @@ -233,6 +234,8 @@ const OPERATIONS: Record = Object.setPrototypeOf({ ...tpl_ops("nat_", "add:$1 double:$0", "nat_chk(e, $0 + $o)", "nat_chk($0 + $o)"), nat_mul: { C: "nat_mul(e, $0, $1)", JS: "nat_chk($0 * $1)" }, + nat_pow: { C: "nat_pow(e, $0, $1)" }, + map_msb_u: { C: "map_msb_u($0, $1)" }, nat_cmp: { C: "(($0 > $1) + ($0 >= $1))", JS: "cmp_new($0, $1)" }, ...tpl_ops("nat_", "sub", "($0 < $1 ? 0 : $0 - $1)"), ...tpl_ops("nat_", "is_lt:<", "($0 $o $1)"), @@ -384,6 +387,22 @@ INLINE u64 nat_mul(Env e, u64 a, u64 b) { return nat_chk(e, b != 0 && a > NAT_IMM / b ? NAT_IMM + 1 : a * b); } +INLINE u64 nat_pow(Env e, u64 a, u64 n) { + u64 r = 1; + while (n > 0) { + if (n & 1) r = nat_mul(e, r, a); + n >>= 1; + if (n) a = nat_mul(e, a, a); + } + return r; +} + +INLINE u64 map_msb_u(u64 n, u32 x) { + if (n == 0 || x == 0) return 0; + u64 width = 32 - CLZ(x); + return n < width ? n : width; +} + #if DEVICE #define f32_show(e, x) (err_post(e.mem, ERR_FIDS), 0) diff --git a/guide/GUIDE.md b/guide/GUIDE.md index 56f91fcb3d..87d48692c3 100644 --- a/guide/GUIDE.md +++ b/guide/GUIDE.md @@ -638,10 +638,10 @@ A `Nat` literal past `256n` is `U32.to_nat(n)` underneath, up to `4294967295n`. ## Under the Hood -Bend's compiler emits one C file, and that file is both the CPU program and the -GPU kernel. clang compiles it for the CPU. Metal (on Apple) or CUDA (on NVIDIA) -compiles the same file for the GPU. So a `!` call runs the same code on -whichever chip it lands on. +Bend emits one C file for CPU and GPU: clang builds the CPU program, +Metal the Apple kernel, CUDA the NVIDIA kernel. Native scalar paths keep +Base's definitions: checked repeated squaring for `Nat.pow`, count-leading-zero +queries for `U32.log2` and fuel-bounded `Map.msb.u`. A term is one 64-bit word: small values are stored inline, everything else is a pointer into a single heap shared by every core and by the GPU. There is no diff --git a/tests/run/nat_power_overflow.bend b/tests/run/nat_power_overflow.bend new file mode 100644 index 0000000000..0bbf53f088 --- /dev/null +++ b/tests/run/nat_power_overflow.bend @@ -0,0 +1,8 @@ +# Repeated squaring must check the 48-bit Nat wall, not just u64 overflow. +import Base + +def main() -> IO(Unit): + IO.print(Nat.show(Nat.pow(2n, 48n))) + +#|bend: a Nat past the largest immediate 2^48-1 +#|exit 1 \ No newline at end of file diff --git a/tests/run/nat_power_wide_overflow.bend b/tests/run/nat_power_wide_overflow.bend new file mode 100644 index 0000000000..11d2d82c3e --- /dev/null +++ b/tests/run/nat_power_wide_overflow.bend @@ -0,0 +1,9 @@ +# Squaring the largest immediate must fail before the product wraps u64. +import Base + +def main() -> IO(Unit): + largest = Nat.add(Nat.mul(16777215n, 16777216n), 16777215n) + IO.print(Nat.show(Nat.pow(largest, 2n))) + +#|bend: a Nat past the largest immediate 2^48-1 +#|exit 1 \ No newline at end of file diff --git a/tests/run/native_numeric_boundaries.bend b/tests/run/native_numeric_boundaries.bend new file mode 100644 index 0000000000..18ef86afdc --- /dev/null +++ b/tests/run/native_numeric_boundaries.bend @@ -0,0 +1,53 @@ +# Native scalar paths agree with Base at zero, bit-width and Nat boundaries. +import Base + +def main() -> IO(Unit): + +largest = Nat.add(Nat.mul(16777215n, 16777216n), 16777215n) + do IO: + IO.print(Nat.show(Nat.pow(0n, 0n))) + IO.print(Nat.show(Nat.pow(0n, 7n))) + IO.print(Nat.show(Nat.pow(3n, 7n))) + IO.print(Nat.show(Nat.pow(1n, 257n))) + IO.print(Nat.show(Nat.pow(2n, 47n))) + IO.print(Nat.show(Nat.pow(largest, 0n))) + IO.print(Nat.show(Nat.pow(largest, 1n))) + IO.print(Nat.show(U32.log2(0))) + IO.print(Nat.show(U32.log2(1))) + IO.print(Nat.show(U32.log2(2))) + IO.print(Nat.show(U32.log2(3))) + IO.print(Nat.show(U32.log2(4))) + IO.print(Nat.show(U32.log2(2147483647))) + IO.print(Nat.show(U32.log2(2147483648))) + IO.print(Nat.show(U32.log2(4294967295))) + IO.print(Nat.show(Map.msb.u(0n, 4294967295))) + IO.print(Nat.show(Map.msb.u(1n, 0))) + IO.print(Nat.show(Map.msb.u(1n, 4294967295))) + IO.print(Nat.show(Map.msb.u(31n, 4294967295))) + IO.print(Nat.show(Map.msb.u(32n, 4294967295))) + IO.print(Nat.show(Map.msb.u(99n, 4294967295))) + IO.print(Nat.show(Map.msb.u(32n, 1))) + IO.print(Nat.show(Map.msb.u(99n, 2147483648))) + +#|1 +#|0 +#|2187 +#|1 +#|140737488355328 +#|1 +#|281474976710655 +#|0 +#|0 +#|1 +#|1 +#|2 +#|30 +#|31 +#|31 +#|0 +#|0 +#|1 +#|31 +#|32 +#|32 +#|1 +#|32 \ No newline at end of file