Core library
Every Reed program gets a small set of numeric methods for free. They are not imported and not declared anywhere in your source: the compiler injects a default core library into every compilation unit before your own declarations (spec section 5.1).
Browse the full core library reference →
That page is generated by reedc doc from the core library's own source, and served
exactly as the tool writes it — same HTML, same stylesheet, no theming from this site. It
doubles as a worked example of what reedc doc produces for your own
code.
What is in it
Forty-two methods, on the four numeric types. They exist because ordinary Reed syntax cannot spell the underlying WebAssembly instruction:
| Group | Methods | Why a method |
|---|---|---|
| Bit counting | clz, ctz, popcnt | no operator exists |
| Rotation | rotl, rotr | <</>> shift, they do not rotate |
| Unsigned arithmetic | div_u, rem_u | / and % are signed |
| Unsigned comparison | lt_u, le_u, gt_u, ge_u | <, <=, >, >= are signed |
| Wide multiply | mul_wide_s, mul_wide_u | * discards the high half |
| Float math | abs, sqrt, ceil, floor, trunc, nearest | no operator exists |
| Float pairs | min, max, copysign | no operator exists |
i32 and i64 get the integer groups; f32 and f64 get the float ones. Wide multiply is
i64-only, since its result is a pair of i64s.
export func check as "check";
func check() -> i32 {
let flags: i32 = 40;
let leading: i32 = flags.clz(); // 26
let bits: i32 = flags.popcnt(); // 2
let half: f64 = (2.0).sqrt(); // 1.414...
return leading + bits + (half > 1.0 ? 0 : 1) - 28;
}
They cost nothing
Every core method is @inline, so a call is replaced by its body — a single wasm.*
instruction — and the function itself is never emitted. There is no $i32.clz in the
output, no call, and no import:
;; inlined i32.clz(...)
(block $inline_i32_clz_1 (result i32)
(local.set $self_2 (local.get $x_0))
;; return wasm.i32.clz(self);
(br $inline_i32_clz_1 (i32.clz (local.get $self_2))))
The instruction executed is exactly i32.clz, the same one wasm.i32.clz(x) emits. The
block around it is how inlining binds parameters and gives return somewhere to jump to
(see @inline); an engine
optimizes it away. Writing the escape hatch directly produces the flatter
(i32.clz (local.get $x_0)), if you are reading the WAT and want the shorter form.
Being un-emitted is also why you cannot take a reference to one — there is no function to point at:
type error: cannot take a reference to '@inline' function 'i32.clz': its calls are
substituted, so no callable function exists to reference
Signed and unsigned are different operations
WebAssembly integers carry no signedness; the instruction decides. Reed's operators are all signed, so the unsigned versions need a name. The difference is not cosmetic:
export func check as "check";
func check() -> i32 {
let a: i32 = -1;
let signed: i32 = a / 2; // -0, signed division
let unsigned: i32 = a.div_u(2); // 2147483647, -1 read as 4294967295
let cmp: i32 = a.lt_u(1); // 0, since -1 unsigned is the largest value
return signed + cmp + (unsigned == 2147483647 ? 0 : 1);
}
If you are working with values that are conceptually unsigned — sizes, indices, hashes —
reach for the _u methods deliberately. Nothing in the type system will catch the mistake,
because i32 is i32 either way.
Two results from a wide multiply
* on i64 discards the top half of the product. mul_wide_s/mul_wide_u keep it, and
return both halves at once:
let (low, high): (i64, i64) = a.mul_wide_u(b);
Low comes first. Multiplying 2^32 by 2^32 gives low == 0 and high == 1, the
product being exactly 2^64. This needs the wide-arithmetic proposal in the engine
running your module.
add128/sub128 deliberately have no method form: they take four operands (a 128-bit
value is an i64 pair on each side), so a receiver would be one arbitrary quarter of the
arithmetic. Reach for wasm.i64.add128(...) there.
Rounding has four different answers
floor, ceil, trunc, and nearest disagree, and nearest is the one that surprises
people — it breaks ties to even, not away from zero:
| Input | floor | ceil | trunc | nearest |
|---|---|---|---|---|
1.7 | 1.0 | 2.0 | 1.0 | 2.0 |
-1.7 | -2.0 | -1.0 | -1.0 | -2.0 |
2.5 | 2.0 | 3.0 | 2.0 | 2.0 |
3.5 | 3.0 | 4.0 | 3.0 | 4.0 |
min and max also have a WebAssembly-specific rule worth knowing: if either operand is
NaN the result is NaN, which is not what a <-based comparison would give you.
You cannot redeclare one
Core methods occupy the same function namespace as your declarations, so func i32.clz(...)
in your own source is a duplicate-name error, not an override:
resolution error: 'i32.clz' is already declared in '<reed:core>'
That applies only to the qualified name. Point.clz and Counter.sqrt are perfectly legal
— core owns i32.clz, not the word clz.
Core is not the standard library
They are different things and the distinction is normative (spec section 5.2):
| core | standard library | |
|---|---|---|
| arrives | injected into every unit | use std.math.*; |
| contains | one instruction per method | real algorithms, types, recursion |
@inline | always | never |
| emitted | never | only what you reach |
Core exists because some WebAssembly instructions have no Reed syntax. The standard library
exists because programs need sorting, hash maps, and strings. If you are looking for gcd,
sort, or Str, you want the standard library.
See also
- The generated reference — every method, with its signature, description, and parameters.
- Standard library — the opt-in
std:modules, which are a different mechanism entirely. - Methods on types — declaring your own, which uses the same mechanism core does.
- Low-level operations — the
wasm.*escape hatch these methods wrap, and the much larger set of instructions with no method form. reedc doc— the generator that produced the reference page.