Skip to main content

Built-ins, Unicode and compiler usage

29. Built-in Functions and Intrinsics​

Unary Functions​

FunctionDescription
abs(x)Absolute value
neg(x) / -xNegate
not(x)Logical NOT (like !x)
sqr(x)Square
pop(x)Population count (count set bits)
clz(x)Count leading zeros of a 16-bit integer
ctz(x)Count trailing zeros of a 16-bit integer
log2(x)Logarithm of a compile-time operand
isnan(x)-1 if x is a NaN, 0 otherwise
__builtin_norm(x)x with its exponent set to 0: the significand, in [1, 2), with the sign of x (__builtin_norm(3.0) is 1.5)
rand()Pseudo-random bits from the hardware generator, as wide as the destination
control(x)Create control wavelet
transform(x)Transform wavelet

To get 1 for true from isnan or isunordered, store the result in a u1, or write isnan(x) ? 1 : 0.

rand() gives 16 bits into an xp and 32 bits into a u32; with no destination it is xp. Assigned to an sp, the bits are read as a float, so the value can have any exponent and can be a NaN. Draw an integer and convert it to build a numeric distribution.

pop, clz and ctz count the bits of a 16-bit integer (xp, s16 or u16); clz(0) and ctz(0) are 16. A run-time u32 or s32 operand fails to compile with a code-generator message such as Couldn't match the instruction!, not a type error.

log2(x) folds at compile time: an integer operand gives the floor of its base-2 logarithm (log2(9) is 3), a float operand its logarithm (log2(0.5) is -1.0). An operand that is not above zero is rejected, and so is a run-time operand (log2 takes a compile-time operand).

NEG, ABS, SQR and NORM accept only the operand types in the table below, on a scalar or on one element per lane. There is no implicit cast, widening, or narrowing. The one mixed form is sqr((ep)s) of an sp value s, stored to an sp: the square is meant to execute in ep (FSQRE), but Carbide currently emits the sp square. norm(x) is not an alias for __builtin_norm(x). The types accepted on SDR are:

OperationTypes
-x, neg(x)s16/xp, u16, s32, u32, hp, sp
abs(x)s16/xp, u16, hp, sp
sqr(x) floatinghp, ep, sp
__builtin_norm(x)hp, sp

Every other type or shape is rejected. Carbide does not support integer sqr(x). Integer NEG is modular in the destination width. Floating NEG flips the sign bit, ABS clears it, and floating SQR performs one native square at the declared precision. NORM uses the target instruction's specified special-value and subnormal behavior. math.fast never changes this matrix.

abs reads an integer as signed: abs of the u16 value 40000 is 25536.

Binary Functions​

FunctionDescription
max(a, b)Maximum
min(a, b)Minimum
round(x, m)x converted to an xp with rounding mode m
builtin_dot(a[i], b[i])Inside i in [0, N), the dot product of a and b: the same as ∑ i in [0, N) a[i] * b[i]
isunordered(a, b)-1 if a or b is a NaN, 0 otherwise

round(x, m) takes a float x and a constant mode m: 0 rounds to nearest even, 1 toward zero, 2 toward −∞ and 3 toward +∞. round(2.5, 0) is 2 and round(-2.5, 2) is -3. A value out of range saturates to 32767 or -32768. The result is an xp; storing it into a float is rejected. A mode read at run time is accepted but ignored: the conversion rounds to nearest even.

min and max first apply Tungsten's Implicit Type Coercion, not C's promotion: mixing xp and u16 compares unsigned, so max(x, u) with xp x = -8 and u16 u = 3 is -8. Floating-point forms use the target's native operation. Integer forms are lowered to a compare-and-select library implementation when that width is available: xp, s16, u16, s32 and u32 on SDR.

The __builtin_-prefixed forms are listed under Hardware Intrinsics below. The prefix is part of the name: copysign and norm on their own are not accepted.

Other Built-ins​

ExpressionDescription
break(0)Raise a hardware breakpoint
__builtin_nop()No operation
2 ** xPower of two
a.baseBase address of array

Hardware Intrinsics​

The __builtin_ family exposes hardware operations that have no operator spelling. They are expressions, except __builtin_nop(), which is a statement. The prefix is part of the name.

IntrinsicArgsDescription
__builtin_copysign(a, b)2a with the sign of b
__builtin_norm(x)1Normalize x
__builtin_merge(hi, lo)2Put hi in bits 31:16 and lo in bits 15:0 of a 32-bit word — used to build a wavelet before a socket send; for an indexed wavelet, hi is the index
__builtin_divstarter(src0, src1, dst)3Divide starter — see below
__builtin_fcpxstarter(src0, table, dst)3Complex-function starter — see below
__builtin_nop()0Emit a no-op

__builtin_merge accepts exact width contracts. SDR supports an ordinary 16+16-to-32 concatenation. SDR's 64-bit form is MERGES32: a 16-bit sparse index plus 32-bit datum written to a non-GPR 64-bit DSR destination, with layout {index,data[31:16],index,data[15:0]}. SDR rejects ordinary 32+32-to-64 merge. All other width combinations are unsupported.

Starter Intrinsics​

__builtin_divstarter and __builtin_fcpxstarter correspond to the hardware's divide and complex-function starter instructions. Carbide supports only their sp forms (FDIVSS and FCPXSS). They exist to get the corner cases out of the way before the iterative body runs, and they are unusual in producing two results:

  • the return value is a u1 predicate. For __builtin_divstarter it is 1 only on a special case (NaN, infinity, divide-by-zero, a zero operand) and 0 on the normal path; for __builtin_fcpxstarter it is set as its table of actions directs for each class of operand;
  • the third argument is a 32-bit integer destination that receives the payload — on the normal path a seed for the iterative body, and on the special path the finished qNaN / infinity / zero result. For __builtin_fcpxstarter it must be an s32.

For __builtin_fcpxstarter, src0 is the function operand and the second argument is a 32-bit integer table of actions selecting what to do for each class of operand. The table holds eight 4-bit actions, one per class of src0, from bits 3:0 to bits 31:28: signalling NaN, quiet NaN, +∞, −∞, +0, −0, a positive number and a negative number. Action 3 selects the normal path. Actions 8, 0xA and 0xE return a NaN, +∞ and −∞, and 9 and 0xD return +0 and −0; each sets the predicate. With u32 ACTION_TABLE = 0x83EE8A88;, the table of a logarithm's starter, 4.0 takes the normal path with payload 2, 0.0 gives −∞ and -1.0 gives a NaN.

When the predicate marks the special cases, the normal body is guarded by its negation:

[[vectorize.pref_len(N), vectorize.iter(j)]]
∀i in [0, N) {
if (!__builtin_fcpxstarter(src[i], ACTION_TABLE, payload[i])) {
dst[i] = src[i] * SCALE; // normal path only
}
}

Inside a vectorized loop the predicate is per-lane, so it cannot be held in a scalar — several lanes would write the same variable. Carbide does not reject a scalar u1 here, but the lanes then do not see their own predicates, and some take the wrong branch without a warning. Save the predicate to a u1 array indexed by the auxiliary iterator when it is needed after the call:

u1 flag[n=N];

[[vectorize.pref_len(N), vectorize.iter(j)]]
∀i in [0, N) {
flag[j] = __builtin_divstarter(src0[i], src1[i], out[i]);
if (flag[j]) { special[i] = 1; }
if (!flag[j]) { normal[i] = 1; }
}

30. Unicode Support​

Tungsten uses Unicode for mathematical readability. Most forms have an ASCII spelling; a descending iterator, for example, is i↓ in [0, N) or i in (N, 0].

Mathematical Symbols​

UnicodeASCII EquivalentMeaning
←<-Assignment
←̟/⬲/⥆<+- / +=Accumulate assignment
∀(none)For-all (loop prefix)
∈/∊inSet membership (loop)
∞INFInfinite iterator end
↓(end, start] rangeDecreasing iterator
×*Multiplication
⨁/⊕^XOR
⬶<<Shift left
⤖x>> / ~>>Arithmetic shift right
⤅0>> / >>Logical shift right
∧&&Logical AND
∨||Logical OR
¬!Logical NOT
≠!=Not equal
≤/≦<=Less or equal
≥/≧>=Greater or equal
∑(prefix)Dot product sum

Only the symbols in the table are accepted. A let iterator is applied with ∀, which has no ASCII spelling; an iterator declared inline, as in i in [0, N) a[i] = 0;, needs no ∀ (see Unicode For-All).

Greek Letters in Identifiers​

Tungsten supports Greek letters as identifiers:

hp α; // alpha
sp β = 2.0; // beta
xp Δx = 1; // delta-x

Combining Marks and Superscripts​

Identifiers can include diacritical marks and superscript or subscript digits:

xp x²; // x-squared (superscript)
sp v⃗; // v-vector (combining arrow)
hp x′; // x-prime (U+2032 PRIME)

31. Compiler Usage​

Basic Compilation​

carbide source=mycode.w

This writes the generated Yeti, the debug information and the address map to mycode_artefacts/, and no image; add image= for one (see Output Files).

carbide help lists every option, and carbide version prints the compiler's version. Option Reference describes each option.

Architecture Target​

carbide source=mycode.w arch=sdr # WSE-3/CS-3

Setting Parameters​

carbide source=mycode.w set=N=16 set=M=32

set= gives a declared param a number; a name the kernel does not declare is ignored. A kernel with sockets, compiled on its own, also needs a color and a bus for each socket (see Sockets):

FormEffect
set=rx=3Socket rx gets color 3 on bus 3
set=rx={.color=3,.bus=1}Color 3 on bus 1. The fields may come in either order, and both are needed
set=s={{.color=2,.bus=1},{.color=4,.bus=3}}The first pair is the socket's input, the second its output
set=t=[{.color=1,.bus=2},{.color=3,.bus=4}]One pair for each element of a socket array

Quote a form with braces in a shell. Paint passes these forms itself. A kernel with a dispatch block also needs the message definitions that Paint passes as rpco=, so compile it through Paint.

Output Files​

carbide source=mycode.w image=output.img symbols=output.sym
carbide source=mycode.w listing=output.lst

The generated Yeti goes to mycode_artefacts/mycode0.yeti; wasm=output.yeti renames it to mycode_artefacts/output0.yeti.

Multiple Source Files​

Each invocation compiles exactly one source file; a second source= is rejected. A bare mycode.w as the first argument is the same as source=mycode.w, and a .yeti source is assembled rather than compiled. Code in other files is brought in with import (see Imports). libpath=<dir> adds a directory to search for imported packages:

carbide source=mycode.w libpath=pkgs # import mylib.util finds pkgs/mylib/util.w

Debugging​

carbide source=mycode.w verbosity=stats # the register allocator's summary and each stage's time
carbide source=mycode.w verbose # those, each pass's time, and the stage messages
carbide source=mycode.w debug_passes=all # trace every pass to stdout
carbide source=mycode.w wall # the optional warnings

A compile that succeeds prints nothing unless verbosity=stats, verbose or a debugging option asks for output.

Optimization Flags​

carbide source=mycode.w Os # cap loop_versioning_size_threshold at 0
carbide source=mycode.w Oz # cap it at -5

loop_versioning_size_threshold=<n> is the number of instructions a loop version may add; its default is 0, so at the default Os changes nothing.

Array Bounds Verification​

By default Carbide rejects one kind of out-of-bounds access: a ∑ whose affine subscripts, over constant iterator ranges, would read outside an operand array (∑ reads outside 'a'). It neither rejects nor warns about any other out-of-bounds index, even a constant one and even with wall: on xp a[n=4], a[7] = 9; compiles and writes whatever follows a. Compile with fsanitize=bounds while developing. It inserts a runtime assertion on every array access the compiler cannot prove in bounds, outside parallel arms; a failing assertion stops at a breakpoint:

carbide source=mycode.w fsanitize=bounds

no_verify_array_bounds lets an access reach past its array's declared extent into the rest of its storage; under it, fsanitize=bounds checks nothing.

carbide source=mycode.w no_verify_array_bounds

This option does not make an out-of-bounds access safe or reserve backing memory for that access.

Option Reference​

Every option carbide help lists, in its groups. Paint passes arch, set, source, libpath, rpc, rpco, image, symbols, listing, wasmlisting, debuginfo and ucid itself; the rest are for compiling or debugging a kernel by hand.

Compilation modes. Paint compiles a kernel in two steps with these:

OptionEffect
rpc=<file>Write the RPC interface file that Paint reads, then stop
rpco=<file>Read Paint's RPC file and rewrite each RPC call from it
rpc_buffer_threshold=<n>With rpco: an RPC call with at least n lambda wavelets sends them from a buffer. Default 2

Source input:

OptionEffect
source=<file>The input: a .w file, or a .yeti file to assemble. One per compile; a bare <file>.w as the first argument is the same
libpath=<dir>A package search root for import. Repeatable; searched in command-line order, after the source's directory and before the installed packages

Architecture:

OptionEffect
arch=sdrTarget Schrödinger (WSE-3/CS-3). The default

Output files:

OptionEffect
image=<file>The executable image
symbols=<file>The symbol table
listing=<file>The assembly listing with addresses; with wasmlisting, the source listing instead
wasmlisting=<file>With listing: the assembly listing with addresses
debuginfo=<file>A copy of the debug information (JSON, see Debug information) that <source>_artefacts always gets
wasm=<file>Name the Yeti file in <source>_artefacts after this file's stem instead of the source's
ast_json=<file>The final AST as JSON, or the AST that a failed verification rejected
ucid=<string>The suffix of each <source>_artefacts file name, also recorded in the debug information. Default 0

Register allocation:

OptionEffect
alloc_gprs=<n,...>The general-purpose registers (0-15) the allocator may use. Registers the call convention places are used regardless. Default: all 16
alloc_dsra=<n,...>The DSR slots (0-15) of bank a (T0X) the allocator may use. Default: all 16
alloc_dsrb=<n,...>The same for bank b (T1X)
alloc_dsrc=<n,...>The same for bank c (T2X)

Optimization:

OptionEffect
OsOptimize for size: cap loop_versioning_size_threshold at 0
OzMinimize size: cap loop_versioning_size_threshold at -5
loop_versioning_size_threshold=<n>The number of instructions a loop version may add. Every version adds some, so at the default, 0, no loop is versioned
ir_functionsKeep function calls as calls, then copy the small ones and those called from only one place. Calls to inline functions, the compiler's routines behind operators, and calls that a call cannot carry are always copied; see Inline Functions. On by default
no_ir_functionsCopy every function call; a program with recursion is then rejected. ir_functions wins when both are given
skip_ir_pass=<pass,...>Leave the named IR passes and machine optimizations out of the pipeline

Checking:

OptionEffect
fsanitize=boundsAssert each array access not proven in bounds, outside parallel arms (see Array Bounds Verification)
no_verify_array_boundsLet an access run past its array into the rest of its object; turns fsanitize=bounds off
no_verify_astSkip verifying the AST after parsing and after the AST passes
verify_ast_unchangedFail an AST pass that reports no change but changes the AST
eval_irEvaluate the AST, its IR, and the IR after each IR pass, and fail where a global's value changes
wallEnable the optional warnings

Debugging:

OptionEffect
debugTrace the AST and Yeti pass managers to stdout
debug_passes=<pass,...>Trace only the named AST and Yeti passes to stdout, or every pass with all
debug_only=<pass,...>Like debug_passes; the first name also turns on that pass's own output, for example const-folding
print_ir=<pass,...>Dump the IR to stderr after the named IR passes, or after every one with all
print_mir=<pass,...>Dump the machine function after the named machine passes, for example phi-elim, or after every one with all
print_ast=<pass,...>Dump the AST to stderr after the named AST passes; parse gives the parser's AST and all every pass's
dump_pass_graphPrint the AST pass pipeline to stdout as a DOT graph, and exit
debug_contextStart every task with as many NOPs as a task-table entry holds
verbosity=quiet, =stats, =progressWhat a successful compile prints to stderr: nothing (the default); the register allocator's summary and each stage's time; or those plus each pass's time and the stage messages
verboseThe same as verbosity=progress
pass_pipeline_verbosePrint the name of each AST and Yeti pass to stdout as it runs
vlowerprimPrint the AST before and after each lowering to primitives
versionPrint carbide <commit[-dirty]> (<branch>, <timestamp>) and exit; carbide version does the same

Parameters: the set= forms in Setting Parameters.

Flags that carbide help marks as ignored have no effect.