Storage Classes
Contents
Storage Classes¶
This document describes storage classes in CSL.
Overview¶
By default, any variable declared in a CSL program is accessible only within
the program itself. This means that when linking two separately-compiled
objects A
and B
, variables declared in the source code for A
will
not be visible in the source code for B
, and vice versa. It also means
that tools manipulating symbols in the compiled binary (such as readelf
or
the Cerebras ELFLoader
class in Python) will not necessarily be able to
access the contents of the symbol by name.
CSL features two storage classes for global variables and functions that modify
this default behavior: extern
and export
.
extern¶
The extern
storage class declares that a symbol for a variable or function,
with a certain type, is expected to be defined in an export
declaration
elsewhere. That export
declaration may either be in the object file
currently being compiled, or in another object file that will be linked with
the object being compiled. Declaring a variable with extern
makes that
variable or function available for use within the current source file, under
the declared name.
A variable declaration with the extern
storage class must not have an
initializer expression, and a function declaration with the extern
storage
class must not have a body block.
Declarations with extern
storage must have export-compatible types. See
Type restrictions for more details.
Currently, no calling convention for extern
functions is formally defined.
Calling extern
functions at comptime is not allowed.
Examples:
extern var x: i16;
extern var y: *const fn(i16) i16;
extern const z: [*]f16;
extern fn f() void;
extern fn g(x: i16, y: [*]f16) i16;
export¶
The export
storage class defines a variable or function with a certain
name and type, and makes that variable or function available to other object
files that are linked with the object being compiled.
A variable declaration with the export
storage class may have an
initializer expression, but it is not required. A function declaration with
the export
storage class must have a body block.
Declarations with export
storage must have export-compatible types. See
Type restrictions for more details.
Currently, no calling convention for export
functions is formally defined.
In addition to making symbols accessible by other objects, export
ensures
that the symbol will be accessible via the Python ELFLoader
class (or
other tools that operate on ELF files). If a symbol is intended to be accessed
either in the compiled binary or in a post-execution core dump via
ELFLoader
, that symbol must be declared export
.
Examples:
fn internal_func(x: i16) i16 { return x+1; }
export var x: i16 = 42;
export var y: *const fn(i16) i16 = &internal_func;
export const z: [*]f16;
export fn f() void { x += 1; }
export fn g(x: i16) i16 { return x+2; }
Object file symbols for extern
and export
declarations¶
Unless a linkname
is supplied (see below), the symbol name for an
export
or extern
declaration will be the same as its CSL variable
name, even if the variable is declared inside an imported module.
Additionally, export
or extern
symbols will be part of a single
global namespace shared with all other objects with which the compiled object
is linked. For example:
///// Source file "root.csl"
// Symbol name in the compiled object file will be "x", and "x" will
// have global scope.
export var x: i16 = 32;
const m = @import_module("submodule.csl");
///// Source file "submodule.csl"
// Symbol name in the compiled object file will be "y", and "y" will
// have global scope.
export var y: i16 = 42;
// Symbol name in the compiled object file will be "z", and "z" will
// have global scope. Its actual definition is expected to be supplied by
// another object.
extern var z: i16;
Interaction with linkname
¶
Both export
and extern
declarations may be assigned a linkname
.
If a linkname
is present, that name is used for the symbol in the object
file. For example:
// Symbol name in the compiled object will be "foo", and "foo" will have
// global scope, i.e., will be accessible by other objects.
export var x: i16 linkname("foo") = 99;
// Note that within the current program, the name "x" must still be used.
...
x += 1; // valid
foo += 1; // invalid
...
This can be useful when export
declarations are present in code that is
intended to be imported with @import_module
. Here, comptime determination
of the linkname
can be used to avoid clashes among symbol names. For
example:
///// Source file "root.csl"
const m1 = @import_module("submodule.csl", .{ .sym_name = "m1_foo" });
const m2 = @import_module("submodule.csl", .{ .sym_name = "m2_foo" });
...
m1.x // corresponds to the object symbol "m1_foo"
m2.x // corresponds to the object symbol "m2_foo"
...
///// Source file "submodule.csl"
param sym_name: comptime_string;
// Without the variable linkname, two imported copies of "submodule.csl"
// would always have symbol naming conflicts, since they would both
// attempt to define the object symbol "x".
export var x: i16 linkname(sym_name);
Mixing extern
and export
declarations¶
It is legal to have multiple extern
declarations with the same object
symbol name within a compiled program, and up to one export
declaration,
as long as all declarations sharing a symbol name agree on declaration kind
(var
, const
, or fn
) and have the same type. Neither an extern
nor an export
declaration may share a symbol name with a declaration that
has no storage class (i.e., one which is neither export
nor extern
).
For example:
///// Source file "root.csl"
// The variable "x" will have a symbol name of "x", since no linkname is
// specified.
export var x: i16;
// The variable "y" will essentially be aliased to "x", since they share a
// symbol name.
extern var y: i16 linkname("x");
const m = @import_module("submodule.csl");
///// Source file "submodule.csl"
// The variable "x" will have a symbol name of "x", since no linkname is
// specified. Since it shares a symbol name with the variable "x" from
// root.csl, it will effectively be aliased to that variable.
extern var x: i16;
// The variable "z" will essentially be aliased to "x", since they share a
// symbol name.
extern var z: i16 linkname("x");
// Note that the following declarations would not be allowed.
// A declaration without a storage class cannot have the same symbol name
// as an 'export' or 'extern' declaration.
// var bad_var_decl: i16 linkname("x");
// All declarations sharing a symbol name must have the same type.
// extern var bad_type: i32 linkname("x");
// Variable declarations sharing a symbol name must match with respect to
// constness.
// extern const bad_constness: i16 linkname("x");
Type restrictions¶
Declarations with export
or extern
storage class must have a type that
is export compatible.
For variable declarations, export compatibility is defined as follows:
The built-in types
i8
,i16
,i32
,i64
,u8
,u16
,u32
,u64
,f16
,f32
,bool
, andcolor
are export compatible.An array type of the form
[N]T
is export compatible if its base typeT
is export compatible.A pointer type of the form
*T
or*const T
is export compatible if its base type is export compatible.Function pointers are a special case: a pointer type of the form
*const T
, whereT
is a function type, is export compatible ifT
is export compatible for a function declaration (see below). (Theconst
qualifier is required.)
enum
types are export compatible.
In particular, note that comptime-only types, struct
types, DSD and DSR
types, the direction
type, the range
type, void
(except in
function return type position–see below), and function types (as opposed to
function _pointer_ types) are not export compatible.
For function declarations, export compatibility is defined as follows:
A function type of the form
fn(t1, t2, ..., tn) t
is export compatible if all of its argument types (t1, t2, ..., tn
) are export compatible for variable declarations (see above) and are allowed for CSL function arguments, and its return typet
either is export compatible and allowed for CSL function return types, or isvoid
.
The restrictions here are motivated by ABI compatibility with C and assembly
code. Note, however, that there is currently no specific calling convention
defined for extern
or export
functions.