Skip to main content

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).

Generated reference

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:

GroupMethodsWhy a method
Bit countingclz, ctz, popcntno operator exists
Rotationrotl, rotr<</>> shift, they do not rotate
Unsigned arithmeticdiv_u, rem_u/ and % are signed
Unsigned comparisonlt_u, le_u, gt_u, ge_u<, <=, >, >= are signed
Wide multiplymul_wide_s, mul_wide_u* discards the high half
Float mathabs, sqrt, ceil, floor, trunc, nearestno operator exists
Float pairsmin, max, copysignno 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:

Inputfloorceiltruncnearest
1.71.02.01.02.0
-1.7-2.0-1.0-1.0-2.0
2.52.03.02.02.0
3.53.04.03.04.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):

corestandard library
arrivesinjected into every unituse std.math.*;
containsone instruction per methodreal algorithms, types, recursion
@inlinealwaysnever
emittedneveronly 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.