Beginner

Hello World in BLISS

Your first BLISS program - the classic Hello World example with Docker setup using the blissc compiler

Every programming journey starts with Hello World. BLISS is a systems programming language with no built-in I/O, so our Hello World requires two files: a BLISS module and a small C wrapper. Let’s see how it works.

The Code

BLISS programs are organized as modules and have no built-in print function. To output text, we declare an external C function and call it from BLISS. The blissc compiler uppercases all identifiers, so the C wrapper must use uppercase function names to match.

Create a file named hello.bli:

MODULE hello =
BEGIN

EXTERNAL ROUTINE
    print_hello;

GLOBAL ROUTINE hello_world : NOVALUE =
BEGIN
    print_hello()
END;

END
ELUDOM

Create a file named wrapper.c:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <stdio.h>

/* blissc uppercases all symbols, so BLISS 'hello_world' becomes 'HELLO_WORLD' */
void HELLO_WORLD(void);

/* BLISS 'print_hello' becomes 'PRINT_HELLO' */
void PRINT_HELLO(void) {
    puts("Hello, World!");
}

int main(void) {
    HELLO_WORLD();
    return 0;
}

Understanding the Code

The BLISS Module

  • MODULE hello = - Declares a module named hello. Every BLISS source file is wrapped in a module.
  • BEGIN ... END - Block delimiters (like curly braces in C)
  • EXTERNAL ROUTINE print_hello - Declares a routine defined elsewhere (in our C wrapper)
  • GLOBAL ROUTINE hello_world : NOVALUE = - Defines a routine visible to the linker. NOVALUE means it returns no value (like C’s void).
  • print_hello() - Calls the external C function to print our message
  • ELUDOM - Ends the module. It’s “MODULE” spelled backwards - a BLISS tradition!

The C Wrapper

Since BLISS has no built-in I/O and the blissc compiler’s MODULE (MAIN=...) attribute is not yet implemented, we need a C file that provides:

  1. main() - The program entry point that calls our BLISS routine
  2. PRINT_HELLO() - The I/O bridge function. Note the uppercase name - blissc uppercases all BLISS identifiers in the object file, so print_hello in BLISS becomes the symbol PRINT_HELLO

Symbol Name Convention

The blissc compiler converts all identifiers to uppercase during compilation. This means:

  • BLISS hello_world → object symbol HELLO_WORLD
  • BLISS print_hello → object symbol PRINT_HELLO

The C wrapper must use these uppercase names for the linker to resolve the symbols correctly.

Running with Docker

The easiest way to run BLISS without installing the compiler locally uses our custom Docker image with the blissc compiler. Since BLISS needs a C wrapper for I/O, create both files in the same directory before running:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Create the BLISS source file
cat > hello.bli << 'EOF'
MODULE hello =
BEGIN

EXTERNAL ROUTINE
    print_hello;

GLOBAL ROUTINE hello_world : NOVALUE =
BEGIN
    print_hello()
END;

END
ELUDOM
EOF

# Create the C wrapper
cat > wrapper.c << 'EOF'
#include <stdio.h>
void HELLO_WORLD(void);
void PRINT_HELLO(void) { puts("Hello, World!"); }
int main(void) { HELLO_WORLD(); return 0; }
EOF

# Compile and run with Docker
docker run --rm -v $(pwd):/app -w /app codearchaeology/bliss:latest sh -c 'blissc -o hello.o hello.bli && gcc -o hello wrapper.c hello.o && ./hello'

Understanding the Docker Command

The compilation happens in two steps:

  1. blissc -o hello.o hello.bli - The blissc compiler compiles the BLISS source to a native object file (.o) via LLVM
  2. gcc -o hello wrapper.c hello.o - GCC compiles the C wrapper and links it with the BLISS object file to produce an executable
  3. ./hello - Runs the resulting program

Expected Output

Hello, World!

Why Two Files?

BLISS was designed for systems programming on DEC hardware, where I/O was always handled through operating system calls rather than language primitives. On the original VMS systems, you would use LIB$PUT_OUTPUT to print text. With the modern blissc cross-compiler, we bridge to the C standard library instead.

This two-file pattern is actually common in systems programming - the language handles computation, and a thin wrapper handles platform-specific I/O.

The Dot Operator

One of BLISS’s most distinctive features is that variable names evaluate to their address, not their contents. To get the value stored at a variable, you use the dot (.) operator:

OWN x;
x = 42;          ! Store 42 at the address of x
y = .x;          ! y gets the contents of x (42)
y = .x + 1;      ! y gets 43

This is the opposite of most languages where x gives you the value. In BLISS, x gives you the address, and .x gives you the value. This makes pointer manipulation natural for systems programming.

Module Structure

Every BLISS source file follows this structure:

MODULE name =
BEGIN

! Declarations
OWN counter;
EXTERNAL ROUTINE some_function;

! Routines
GLOBAL ROUTINE my_routine : NOVALUE =
BEGIN
    ! statements
END;

END
ELUDOM

Key elements:

  • OWN - Declares a static (module-level) variable
  • LOCAL - Declares a stack-allocated variable (inside routines)
  • GLOBAL ROUTINE - A routine visible outside the module
  • ROUTINE - A routine private to the module
  • NOVALUE - Indicates the routine doesn’t return a value

Control Flow

BLISS uses structured control flow with no goto:

! If-then-else (produces a value!)
result = (IF .x GTR 0 THEN .x ELSE -.x);

! While loop
WHILE .count GTR 0 DO
BEGIN
    count = .count - 1
END;

! Counted loop
INCR i FROM 0 TO 9 DO
BEGIN
    ! i goes from 0 to 9
END;

Notice the keyword comparison operators (GTR, EQL, LSS, etc.) instead of symbolic ones.

Compilation Process

When you compile a BLISS program with blissc, the process is:

hello.bli → [blissc/LLVM] → hello.o (object file)
wrapper.c → [gcc] → wrapper.o (object file)
                  → hello (linked executable)

The blissc compiler uses LLVM as its code generation backend, producing optimized native code for x86 systems.

A Bit of History

BLISS was created in 1969, three years after BCPL — the common ancestor of both BLISS and C — was designed, and three years before C appeared at Bell Labs in 1972. Both languages aimed to replace assembly for systems programming, and both drew inspiration from BCPL. While C went on to become ubiquitous through Unix, BLISS became the backbone of DEC’s VAX and OpenVMS ecosystem, where it remains in active use through VMS Software, Inc.’s (VSI) ongoing compiler releases.

The original BLISS compiler at CMU was renowned for its optimization techniques. The book The Design of an Optimizing Compiler (1975), written by Wulf and colleagues based on this work, influenced a generation of compiler designers.

Key Takeaways

  1. BLISS modules are wrapped in MODULE name = BEGIN ... END ELUDOM
  2. No built-in I/O - use external C functions for output
  3. The dot operator (.x) reads a variable’s value; the bare name is its address
  4. blissc uppercases all identifiers - C wrappers must use uppercase names
  5. Two-step compilation - blissc produces object files, gcc links them
  6. Keyword operators like GTR, EQL, LSS replace symbolic comparisons
  7. Everything is an expression - even IF and CASE produce values

Running Today

All examples can be run using Docker:

docker pull codearchaeology/bliss:latest
Last updated: