A zero-overhead systems language uniting Go ergonomics with bare-metal performance: zero-GC, direct C-ABI, and ultra-lightweight, WASI-free WebAssembly (normal & concurrent).
Go
83
240 commits
updated Sep 25, 2026
hike-lang)A lightweight, zero-overhead systems programming language targeting Windows, Linux, and WebAssembly.
A systems programming language with Go-like syntax that compiles to LLVM IR or WebAssembly, generating C-ABI compliant shared libraries, standalone executables, and browser-ready Wasm modules.

Chrome DevTools showing source-level breakpoints, local variables, and a function return value while running Hike WebAssembly.
Build Hike with -g and debug the original .hike source instead of stepping
through generated LLVM IR, WAT, or JavaScript glue code. The same source-level
workflow is available for both native and WebAssembly targets:
# Native: debug with the usual GDB/LLDB or VS Code integration.
hikec build -target linux -g main.hike -o main
# WebAssembly: debug the original source in Chrome DevTools and place the
# source-map URL relative to the hosted Wasm files.
hikec build -target wabt -g --source-map-base ./ main.hike -o main.wasm
The WABT debug build writes main.wasm.map beside main.wasm and embeds a
sourceMappingURL custom section pointing to it. Set
--source-map-base=/assets/wasm when the Wasm and map files are served below a
different URL prefix. If DWARF should be the only browser debug mapping, use
--no-source-map; the map file is still generated, but its URL is not embedded
in the Wasm module.
The compiler emits DWARF information for native binaries and Wasm DWARF custom sections for the WABT target. This makes source-line breakpoints, stepping, and local-variable inspection available in the debugger that matches the target:
| Capability | Native build | WebAssembly build |
|---|---|---|
| Debug switch | -g | -g |
| Debug format | DWARF | DWARF in Wasm custom sections |
| Debugger | GDB, LLDB, or VS Code | Chrome DevTools |
| Source view | Original .hike files | Original .hike files in Sources |
| Inspection | Locals and return values | Locals and return values |
Hike keeps the execution model small: there is no garbage collector and no
always-on language scheduler. Runtime support is emitted only when the program
uses a runtime-backed feature. Debug information is likewise opt-in, so release
builds remain lean while -g builds provide the information needed for precise
source-level debugging.
For the browser-oriented WABT flow, Hike generates the Wasm module and the
required runtime.js integration as part of the build. No handwritten
JavaScript glue, Emscripten setup, wasm-bindgen packaging, or source-map
configuration is required to start debugging:
cd examples/browser
make build
make serve
Open the page in Chrome, press F12, and select the .hike source in the
Sources panel. Set a breakpoint on a Hike statement and inspect locals while
the Wasm code is running. The browser example demonstrates the complete flow,
including source-level stepping and return-value inspection.
Note: Hike is under active development. Full native builds and test suites are verified on Windows and Linux using Clang/LLVM, alongside browser/Node.js WebAssembly targets.
The compiler currently supports LLVM IR and WAT generation, native and wasm32/WABT builds, generic type/function specialization, closures with escape analysis, interfaces, collections, inline assembly, region allocation, threadable and concurrent module variables, and an automated native/WASM test suite. Recent implementation work has also established a length-aware string representation with shared substring views, reference counting, and copy-on-write mutation.
Parts of the test suite already use a Hike-implemented compiler as a self-hosted prerequisite. Native self-hosting is therefore exercised, while running the self-hosted Hike compiler after compiling it to WebAssembly still requires validation.
The language and memory designs are actively evolving. Region allocation and area allocation are explicit memory models, with compiler-wide lifetime inference for temporary and shared string buffers implemented alongside them. See the linked design documents below for the exact implementation boundaries.
| Document | Summary |
|---|---|
alloc-region.md | Region allocation, arena lifetime, escape promotion, diagnostics, examples, and limitations. |
area-allocation.md | Area blocks, scoped memory reuse, deep-copy requirements, and native/WASM behavior. |
encoding.md | UTF-8 rules, string and buffer layouts, shared substring views, reference counting, and copy-on-write. |
wasm.md | wasm32 target behavior, JavaScript runtime integration, exports, memory access, and testing. |
concurrency.md | Async tasks, channels, worker synchronization, closure transfer, and generated task bridges. |
thread-variables.md | Threadable and concurrent module variables, visibility, storage, and synchronization rules. |
external-module.md | External module declarations, hikec get, repository checkouts, and release source archives. |
eventloop.md | Event-loop abstractions built on channels, task invocation, and asynchronous result handling. |
build-constraints-and-assembly.md | Build constraints and the inline assembly syntax and lowering rules. |
without_cgo.md | C-ABI integration without cgo, .syso builds, and ownership rules at language boundaries. |
gpu/webgpu/Hike.md | WebGPU-oriented Hike integration and GPU programming notes. |
Hike is a lightweight systems programming language combining Go-style syntax and ergonomics with C-equivalent execution, direct C-ABI compatibility, and no garbage collection.
The Hike compiler (hikec) has two WebAssembly-capable backends. The LLVM
backend lowers Hike to LLVM IR (.ll), allowing Clang/LLVM to apply its
optimization pipeline before producing native binaries or optimized
wasm32-unknown-unknown modules. The WABT backend lowers directly to WAT, the
standard WebAssembly text representation, and uses wat2wasm to produce a
module without an LLVM round trip. This direct path is the foundation for
Hike's WebAssembly DWARF and browser-debugging support.
The compiler builds standalone executables, C-compatible shared libraries (.dll / .so), and WebAssembly modules (.wasm). When exporting library functions, hikec automatically generates corresponding C/C++ header files (.h).
:=), and generic type parameters..h headers for C/C++ host integration.for-range traversal using compile-time stack allocation (alloca).hike.mod declares dependencies with require, and hikec get downloads them into the project-local .hike/deps tree.wasm32 module, or WABT for direct WAT-to-Wasm generation and browser-oriented debugging.--alloc=region groups eligible function-local allocations into bump arenas and releases them in O(1) at the region boundary.area(...) { ... } provides explicit, thread-local scoped storage with bulk release at block exit; values that outlive the block must be copied explicitly.Async combine to make common multithreaded programs easier to express without requiring a garbage collector or an always-on scheduler.-target wabt -g.Hike supports function-level LLVM inline assembly through the __asm__ { ... }
construct. The first line inside the block declares the operand mapping. The
following entries are the assembly template, output constraints, input
constraints, and clobber constraints:
func encryptBlockFast(roundKeys *byte, dst *byte, src *byte) {
__asm__{
params: roundKeys, dst, src
"movups (%2), %xmm0\n",
"movups (%0), %xmm1\n",
"pxor %xmm1, %xmm0\n",
"movups %xmm0, (%1)\n",
"",
"r,r,r",
"~{xmm0},~{xmm1},~{memory}"
}
}
%0, %1, and %2 refer to the operands listed after params:. They are
translated to LLVM inline-assembly operand references; other assembly text is
preserved. Register names such as %xmm0 remain hardware register names.
Clobbers must declare registers and memory modified by the assembly. Native
instructions must be protected with build constraints such as amd64; a
portable implementation should be provided for targets such as wasm32.
The complete build-constraint and assembly rules are documented in
build-constraints-and-assembly.md.
[ .hike Source Code ]
│
▼ (hikec: Go Frontend, Typecheck, Monomorphization)
[ AST & Desugaring ]
├───► Auto-Generated C/C++ Header (.h) [Optional via -header]
├───► LLVM Backend ──► LLVM IR (.ll) ──► Clang/LLVM optimizer
│ ├──► Native executable/library
│ └──► Optimized wasm32 module
│
└───► WABT Backend ──► WAT ──► wat2wasm
└──► Wasm + DWARF/source mapping
└──► Chrome DevTools
Go: 1.21+
LLVM / Clang: 15.0+ (clang and lld in your PATH)
Windows Toolchain: MinGW-w64 GCC runtime (x86_64-w64-windows-gnu)
Make (MinGW / MSYS2 / Linux / macOS)
Python: 3.8+ (for integration test suites)
WABT: wat2wasm in your PATH when using -target wabt
Wasmtime: Used by the WASM test suites to execute generated WebAssembly modules
GDB or LLDB: Required for debugging native binaries built with DWARF debug information; Wasm DWARF debugging is supported through Chrome DevTools
git clone https://github.com/kanryu/hike-lang.git
cd hike-lang
# Run Unit Tests
go test ./...
# Build Compiler CLI Driver
go build -o hikec.exe ./cmd/hikec
Use --export-symbols to write a JSON catalogue of the identifiers found in
the loaded Hike program:
hikec emit-ir --export-symbols symbols.json main.hike
The catalogue contains imported modules, free functions, global variables,
local variables (including parameters), and interface-compatible methods
grouped under fixed_receivers with their receiver type. Built-in interface
capabilities are included in the same group. Structs and interfaces are
included with their members and method sets; structs, interfaces, members,
functions, and fixed receivers use package-qualified names.
Hike supports explicit type declarations and local type inference via :=. Primitive types map to fixed-width representations: int (i64), float64 (double), byte (u8), and bool (i1). The native string type is a length-aware fat value: { i8*, i32, i32 }, occupying 16 bytes on 64-bit targets and 12 bytes on wasm32.
package main
var globalCounter int = 0
const MaxLimit int = 1024
func DemoVariables() {
var a int = 42
var b float64 = 3.14159
var isEnabled bool = true
var msg string = "Hello, Hike!"
// Type inference
count := 100
ratio := 0.75
}
Structs follow C memory layouts without hidden metadata or GC headers. Raw pointer operations do not incur runtime tracking.
package main
type Point struct {
X float64
Y float64
}
type Rectangle struct {
TopLeft Point
BottomRight Point
}
func CreatePoint(x float64, y float64) Point {
return Point{X: x, Y: y}
}
// Pass by pointer to avoid copying
func OffsetPoint(p *Point, dx float64, dy float64) {
p.X = p.X + dx
p.Y = p.Y + dy
}
Functions are first-class constructs and support multiple return values.
package main
func SafeDivide(a int, b int) (int, bool) {
if b == 0 {
return 0, false
}
return a / b, true
}
func DemoFunctions() {
result, ok := SafeDivide(10, 2)
if ok {
// Use result
}
}
Hike supports if statements with short variable declarations, standard 3-clause for loops, for-range iterations, and switch statements.
package main
func DemoControlFlow(values [5]int) int {
sum := 0
// 1. If with initializer
if n := len(values); n > 0 {
sum = sum + 1
}
// 2. 3-clause for loop
for i := 0; i < 5; i = i + 1 {
sum = sum + values[i]
}
// 3. for-range loop over fixed arrays
for idx, val := range values {
if val < 0 {
continue
}
sum = sum + val
}
// 4. Switch statement
status := 200
switch status {
case 200:
sum = sum + 10
case 404, 500:
sum = sum - 1
default:
sum = 0
}
return sum
}
Fixed-size arrays allocate contiguous memory inline. Slices provide dynamic views backed by a three-word header: pointer, length, and capacity.
package main
func DemoArrays() {
// Fixed-size array
var arr [4]int
arr[0] = 10
arr[1] = 20
primes := [3]int{2, 3, 5}
// Slice expression
sub := primes[1:3]
}
Generic functions and struct definitions are specialized into concrete implementations at compile time, eliminating runtime dispatch overhead.
package main
// Generic function with type union constraint
func Min[T int | float64](a T, b T) T {
if a < b {
return a
}
return b
}
// Generic struct
type Pair[K, V] struct {
Key K
Value V
}
func DemoGenerics() {
minInt := Min(10, 20) // Specializes Min__int
minFloat := Min(3.14, 2.71) // Specializes Min__float64
p := Pair[string, int]{Key: "hike", Value: 1}
}
Hike also supports compile-time value parameters for generic types. A const generic declaration must contain at least one type parameter, and const parameters must appear after all type parameters. Const arguments are limited to immediate primitive values or other const-generic parameters; ordinary variables and expressions involving runtime values are rejected.
type Matrix[T, Rows uint, Cols uint] struct {
data [Rows * Cols]T
}
func MatrixValue() int {
var m Matrix[int, 8, 8]
return 8 * 8
}
Matrix[int, 8, 8] is materialized as a concrete compile-time
specialization. The dimensions are available to layout and indexing code, so
the resulting fixed-size matrix has no runtime dimension metadata.
const Rows uint = 8
const Cols uint = 8
var m Matrix[int, Rows, Cols]
See the generic transformation and interface tests for additional examples of type and const-parameter specialization.
std/maps) & Indexing SugarHike provides a generic hash map implementation (std/maps) with syntax sugar for indexing, membership testing, deletion, and for-range traversal.
package main
import "std/maps"
func printf(format string, ...) int
func main() int {
// Initialize map with initial bucket capacity
hmap := maps.New[string, int](8)
// Subscript assignment sugar
hmap["Tokyo"] = 1400
hmap["Osaka"] = 880
hmap["Nagoya"] = 230
// Comma-ok lookup idiom
if val, ok := hmap["Osaka"]; ok {
printf("Osaka population: %d\n", val)
}
// for-range traversal (stack-allocated iterator)
for city, population := range hmap {
printf(" - %s: %d\n", city, population)
}
// Removal and length query
delete(hmap, "Nagoya")
printf("Remaining entries: %d\n", len(hmap))
return 0
}
Any user-defined struct can implement the Map Behavior protocol to enable indexing syntax (obj[k], obj[k] = v, len(obj), delete(obj, k)) and for-range loops.
To eliminate heap allocations during for-range iterations over custom containers, Hike uses a 2-pass protocol:
Pass 1 (Size Probe): InitIterator(nil) returns the state buffer byte size.
Stack Allocation: The compiler issues an alloca instruction on the caller's stack frame.
Pass 2 (Initialization): InitIterator(buf) initializes the allocated state buffer in-place.
Iteration: Next(buf) runs on each step, returning pointers to the current key/value and a continuation flag.
package main
type Entry[K, V] struct {
Key K
Value V
}
type CustomDictionary[K, V] struct {
Entries []Entry[K, V]
}
func (d *CustomDictionary[K, V]) Set(key K, val V) {
for i := 0; i < len(d.Entries); i = i + 1 {
if d.Entries[i].Key == key {
d.Entries[i].Value = val
return
}
}
d.Entries = append(d.Entries, Entry[K, V]{Key: key, Value: val})
}
func (d *CustomDictionary[K, V]) Get(key K) (V, bool) {
for i := 0; i < len(d.Entries); i = i + 1 {
if d.Entries[i].Key == key {
return d.Entries[i].Value, true
}
}
var zero V
return zero, false
}
func (d *CustomDictionary[K, V]) Len() int {
return len(d.Entries)
}
func (d *CustomDictionary[K, V]) Delete(key K) {
for i := 0; i < len(d.Entries); i = i + 1 {
if d.Entries[i].Key == key {
d.Entries[i] = d.Entries[len(d.Entries)-1]
d.Entries = d.Entries[:len(d.Entries)-1]
return
}
}
}
// --- 2-Pass Iterator Methods ---
type DictIterator struct {
Index int
}
func (d *CustomDictionary[K, V]) InitIterator(buf *byte) int {
if buf == nil {
return 8 // sizeof(DictIterator)
}
it := (*DictIterator)(buf)
it.Index = 0
return 0
}
func (d *CustomDictionary[K, V]) Next(buf *byte) (*K, *V, bool) {
it := (*DictIterator)(buf)
if it.Index >= len(d.Entries) {
return nil, nil, false
}
entry := &d.Entries[it.Index]
it.Index = it.Index + 1
return &entry.Key, &entry.Value, true
}
func DemoMapBehavior() {
var dict CustomDictionary[string, int]
// Uses indexing sugar
dict["itemA"] = 100
dict["itemB"] = 200
// Traverses with zero heap allocation
for k, v := range dict {
// ...
}
}
Async schedules a closure for asynchronous execution and returns a typed one-shot task handle. The receive operator <- waits for completion and unpacks the return value or values.
task := Async(func() int {
return 40 + 2
})
result := <-task
Channels are typed synchronization queues. They can be used directly for producer/consumer pipelines, including staged streaming downloads:
type Download struct {
Blocks chan int
}
func (d *Download) InitIterator(buf *byte) int {
return 0
}
func (d *Download) NextChannel(buf *byte) (chan int, bool) {
return d.Blocks, true
}
func download() *Download {
result := &Download{Blocks: make(chan int, 3)}
Async(func() int {
result.Blocks <- 10
result.Blocks <- 20
result.Blocks <- 30
return 0
})
return result
}
func consume() int {
total := 0
stream := download()
for block := range <-stream {
total = total + block
}
return total
}
The InitIterator and NextChannel method pair is the AsyncIterable[T] protocol defined in std/collections. for value := range <-stream receives blocks in channel order and can stop early with break. Both concrete streams and interface-typed streams are lowered through their respective method dispatch paths, including the concurrent WebAssembly runtime.
For memory-heavy or highly concurrent code, combine this protocol with the
memory models described in area-allocation.md and
thread-variables.md. Area blocks provide explicit
scoped storage, threadable variables avoid cross-worker sharing, and concurrent
variables provide synchronized shared state.
Functions can be passed as values, returned from factories, or defined inline as closures.
Reference Capturing: Captured variables maintain reference semantics across calls.
Automatic Heap Promotion: Variables that escape their stack scope (such as parameters returned inside a closure) are automatically promoted to heap allocation (malloc).
Fat Pointer ABI: Function values compile to a two-word structure:
$$\text{FuncValue} \implies { \text{i8* fn_ptr},, \text{i8* env_ptr} }$$
Top-level and stateless functions carry env_ptr = null. Closures carry a pointer to the captured environment.
package main
// 'base' is promoted to heap by escape analysis
func makeAdder(base int) func(int) int {
return func(n int) int {
return base + n
}
}
func main() int {
add100 := makeAdder(100)
result := add100(42) // => 142
counter := 0
increment := func() int {
counter = counter + 1 // Mutates outer variable
return counter
}
increment()
increment()
finalCount := increment() // finalCount == 3
return 0
}
Any top-level function taking POD types or pointers can be exported to C-ABI. Passing -header <name.h> instructs hikec to emit a C/C++ header matching the exported functions.
libcalc.hike)package main
type Vector2D struct {
X float64
Y float64
}
func Add[T int | float64](a T, b T) T {
return a + b
}
func HikeAddInt(a int, b int) int {
return Add(a, b)
}
func HikeAddFloat(a float64, b float64) float64 {
return Add(a, b)
}
func HikeDotProduct(v1 *Vector2D, v2 *Vector2D) float64 {
return (v1.X * v2.X) + (v1.Y * v2.Y)
}
# 1. Emit LLVM IR and C/C++ header
hikec emit-ir -header libcalc.h -o libcalc.ll libcalc.hike
# 2. Build shared library and import library using Clang
clang -shared -O3 -Wl,--export-all-symbols -Wl,--out-implib,libcalc.dll.a libcalc.ll -o libcalc.dll
libcalc.h)#ifndef HIKE_LIBCALC_H
#define HIKE_LIBCALC_H
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifndef HIKE_API
#if defined(_WIN32) || defined(__CYGWIN__)
#define HIKE_API __declspec(dllimport)
#else
#define HIKE_API extern
#endif
#endif
typedef struct Vector2D {
double X;
double Y;
} Vector2D;
HIKE_API int64_t HikeAddInt(int64_t a, int64_t b);
HIKE_API double HikeAddFloat(double a, double b);
HIKE_API double HikeDotProduct(Vector2D* v1, Vector2D* v2);
#ifdef __cplusplus
}
#endif
#endif
main.cpp)#include <iostream>
#include "libcalc.h"
int main() {
int64_t sumInt = HikeAddInt(400, 600);
double sumFloat = HikeAddFloat(1.414, 1.732);
Vector2D v1 = { 3.0, 4.0 };
Vector2D v2 = { 2.0, 5.0 };
double dot = HikeDotProduct(&v1, &v2);
std::cout << "SumInt: " << sumInt << std::endl;
std::cout << "Dot: " << dot << std::endl;
return 0;
}
Compile and link C++ directly:
clang++ -O3 main.cpp libcalc.dll.a -o client.exe
./client.exe
Unlike Go, which requires cgo, preambles, wrapper generation, and runtime stack-switching overhead, Hike interacts with C at zero runtime cost.
Because hikec compiles directly to standard LLVM IR and delegates code generation to Clang, a function declaration without a body (func ...) is emitted as a standard external C-ABI symbol declaration (declare).
You can call standard C library functions simply by declaring their signatures in your .hike source. The Clang backend automatically links the C runtime (libc / MinGW-w64 / MSVCRT) and resolves the symbols directly.
package main
// Declare standard C library functions directly
func printf(format string, ...) int
func puts(str string) int
func main() int {
puts("Hello directly from C libc!")
printf("Formatted number: %d\n", 42)
return 0
}
To integrate third-party C libraries (e.g., SQLite, Raylib, OpenSSL):
Declare the C API: Write matching function signatures without bodies in your Hike code.
Link via Clang: Pass -l<lib> or direct paths to static (.a / .lib) or dynamic (.so / .dll / .dylib) libraries during build.
main.hike)package main
// External library declaration (e.g., libcurl or a custom C library)
func my_c_library_init() int
func my_c_calculate(a int, b int) int
func main() int {
if my_c_library_init() != 0 {
return 1
}
res := my_c_calculate(10, 20)
return 0
}
# 1. Emit LLVM IR
hikec emit-ir -o main.ll main.hike
# 2. Compile and link external C library with Clang
clang -O3 main.ll -L/path/to/libs -lmy_c_library -o app.exe
hike.mod)Hike resolves local dependencies and package roots via hike.mod in the project root.
External Hike packages can be imported by declaring a require directive and
running hikec get. The command downloads the requested project into
.hike/deps, after which its packages are available through normal Hike
imports.
module my-project
hike 0.1.0
require github.com/kanryu/hike-gpu-webgpu v0.1.0
hikec get
When a package should be resolved from a directory relative to the project,
replace can be used. The path is resolved from the project module root; it
does not download or manage an external dependency:
replace github.com/kanryu/hike-gpu-webgpu => ../../gpu/webgpu
Use require with hikec get for project-managed external dependencies, and
use replace when a package should be resolved through a project-relative
directory.
For repository checkouts, release source archives, and dependency refresh
behavior, see external-module.md.
std/slices)The standard library provides generic collection operations for slices, conforming to standard functional and Go-like semantics. All operations are monomorphized at compile time with zero overhead.
Filter[T](s []T, predicate func(item T) bool) []T: Returns a new slice containing elements that satisfy the predicate.Map[T, U](s []T, transform func(item T) U) []U: Returns a new slice where each element is mapped via transform.IndexFunc[T](s []T, predicate func(item T) bool) int: Returns the index of the first element satisfying the predicate, or -1 if not found.Find[T](s []T, predicate func(item T) bool) (T, bool): Returns the first matching element and a boolean flag indicating success.SortFunc[T](s []T, cmp func(a T, b T) int): Performs an in-place quicksort using a three-way comparison function (a < b returns negative, a == b returns 0, a > b returns positive).SortBy[T](s []T, less func(a T, b T) bool): Performs an in-place quicksort using a boolean comparison predicate (a < b returns true).package main
import (
"std/slices"
)
func printf(format string, ...) int
func main() int {
nums := []int{5, 2, 8, 1, 9, 4}
// 1. Filter elements
evens := slices.Filter[int](nums, func(x int) bool {
return x % 2 == 0
})
// 2. Map elements
mapped := slices.Map[int, int](evens, func(x int) int {
return x * 10
})
// 3. Search elements
val, ok := slices.Find[int](nums, func(x int) bool {
return x > 5
})
idx := slices.IndexFunc[int](nums, func(x int) bool {
return x > 5
})
printf("Find: %d (ok: %d), Index: %d\n", val, ok, idx)
// 4. In-place sorting
slices.SortFunc[int](nums, func(a int, b int) int {
return a - b // Ascending
})
slices.SortBy[int](nums, func(a int, b int) bool {
return a > b // Descending
})
return 0
}
std/encoding/json)The standard library provides DOM parsing, traversal, mutation, serialization, and file I/O.
package main
import (
"std/encoding/json"
)
func printf(format string, ...) int
func main() int {
// 1. Read file content
content := json.ReadFile("data.json")
if len(content) == 0 {
printf("Failed to read data.json\n")
return 1
}
// 2. Parse DOM tree
doc := json.Parse(content)
if doc == nil {
printf("Failed to parse JSON\n")
return 1
}
// 3. Access fields
nameVal := doc.Get("name")
verVal := doc.Get("version")
if nameVal != nil {
printf("Name: %s\n", nameVal.AsString())
}
if verVal != nil {
printf("Version: %d\n", verVal.AsInt())
}
// 4. Mutate DOM
doc.Set("modified_by", json.NewString("hikec"))
newStats := json.NewObject()
newStats.Set("active_threads", json.NewNumber(8.0))
doc.Set("stats", newStats)
// 5. Serialize and write back
outStr := json.Stringify(doc)
json.WriteFile("output.json", outStr)
return 0
}
Hike provides two WebAssembly build paths. Choose the backend with
-target:
| Target | Backend | Best for |
|---|---|---|
wasm32 | LLVM/Clang | Optimized, compact, high-performance Wasm modules |
wabt | Direct WAT + wat2wasm | Source-level debugging in browser DevTools |
The wasm32 target lowers through LLVM IR, allowing the LLVM/Clang optimizer
to produce a compact and fast WebAssembly module. The wabt target emits the
standard WebAssembly text representation directly and converts it with WABT.
Because this path keeps the source-to-Wasm mapping under Hike's control, it is
the recommended backend for debugging Hike source in Chrome DevTools. Hike will
continue to strengthen the WABT backend as its primary direct-WebAssembly
pipeline.
Both backends emit the internal runtime support needed by the program and can generate the JavaScript runtime for the selected host mode. The generated Wasm modules are intended to run across WebAssembly hosts including browsers, Node.js, and other runtimes that provide the standard WebAssembly JavaScript API.
The generated WASM modules are intended to run across WebAssembly host
environments, including web browsers, Node.js, and other WASM runtimes that
provide the standard WebAssembly JavaScript API. In a web browser, a module
can be used immediately by referencing the generated runtime.js and the
.wasm binary from an HTML page; the runtime handles instantiation and exposes
the Hike exports to the page's JavaScript.
# Optimized Wasm through LLVM/Clang.
hikec build -target wasm32 -o main.wasm main.hike
# Direct WAT/WABT build for Chrome DevTools source debugging.
hikec build -target wabt -g --source-map-base ./ -o main.wasm main.hike
# The same host model can be exercised through Node.js.
hikec run -target wasm32 main.hike
build produces the .wasm module and automatically writes runtime.js in
the output directory. A WABT debug build also writes main.wasm.map and
embeds its sourceMappingURL custom section unless --no-source-map is
specified. run builds a temporary module and starts Node.js with the
generated runtime. This is the same host model used by the automated WASM
tests.
Use -wasm-mode normal for ordinary exported functions and
-wasm-mode concurrent when the program uses asynchronous tasks, workers, or
directed JavaScript callbacks:
hikec build -target wasm32 -wasm-mode normal -o app.wasm main.hike
hikec build -target wasm32 -wasm-mode concurrent -o app.wasm main.hike
hikec emit-js -target wasm32 -wasm-mode concurrent -o runtime.js main.hike
The generated runtime is reusable from both browsers and Node.js. Browser code
can instantiate the module and call exported cfunc functions in response to
events. Node.js tests load the same generated runtime with require or ES
module import and invoke the exports directly. Each WASM test keeps its
JavaScript driver separate while sharing the runtime implementation.
WASM host functions receive string data through the generated ABI bridge. Hike code can expose byte-pointer and length parameters when a direct host buffer interface is preferred, then construct a Hike string from that pair:
cfunc TransformString(input *byte, length int) cstring {
value := string(input, length)
return cstring(value + " [wasm]")
}
The runtime provides UTF-8 encoding/decoding helpers and access to linear
memory. Hike int values use WebAssembly i64 and should be passed from
JavaScript as BigInt; int32, byte, and bool use i32-compatible values.
Hike strings are length-aware fat values internally, while C-facing exports
are adapted to pointer-based ABI values by the compiler.
import { HikeRuntime } from "./runtime.js";
import fs from "node:fs/promises";
const runtime = new HikeRuntime();
const wasm = await runtime.load("main.wasm");
const result = wasm.AddNumbers(1234, 5678);
console.log(result);
For browser integration, instantiate the generated runtime after loading the
.wasm asset and call exported functions from event handlers. For Node.js,
the tests/wasm suite provides examples covering maps, strings, concurrent
workers, and JavaScript functions (jfunc).
The full host API, memory model, and integration examples are documented in
wasm.md.
Passing -g instructs hikec to emit LLVM DWARF metadata, enabling source-level breakpoints, single-stepping, and variable inspection in VS Code using GDB or LLDB.
.vscode/tasks.json{
"version": "2.0.0",
"tasks": [
{
"label": "Build Hike Debug Executable",
"type": "shell",
"command": "hikec ${file} -g -o${fileDirname}/main.ll && clang -g -O0 ${fileDirname}/main.ll -o${fileDirname}/app.exe",
"options": {
"cwd": "${fileDirname}"
},
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": ["$gcc"]
}
]
}
.vscode/launch.json{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Hike Program (F5)",
"type": "cppdbg",
"request": "launch",
"program": "${fileDirname}/app.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${fileDirname}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"miDebuggerPath": "gdb",
"preLaunchTask": "Build Hike Debug Executable",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
]
}
Pressing F5 compiles the active .hike file and launches the debug session, supporting breakpoints, Step Over (F10), Step Into (F11), and variable inspection.
hikec)Usage: hikec <command> [options] <source.hike... | directory>
Commands:
go Compile a directory of .go.hike files into one .syso object
get Download modules listed in hike.mod into .hike/deps
emit-ir Generate target LLVM IR (.ll) (default for a source input)
emit-js Generate the WebAssembly JavaScript runtime.js bridge
build Compile Hike source into a native or WebAssembly binary via Clang
run Build and immediately execute a native or WebAssembly program
Common options:
-o <path> / -o=<path>
Output path. For emit-ir this is .ll; for emit-js it is
runtime.js; for build it is the executable or .wasm.
-target <name> / --target=<name>
Target triple or shorthand: windows, windows-msvc, linux,
darwin, wasm32, wasm64, or wabt.
-v / --verbose Enable verbose logging.
-vv / --vv Enable detailed instruction-level logging.
Options for emit-ir:
-header <path> / --header=<path>
Generate a C/C++ header for exported declarations.
-wasm-mode <mode> / --wasm-mode=<mode>
WebAssembly runtime mode: normal or concurrent.
--alloc=region / -alloc=region
Enable optional function-scoped region allocation.
-cflags <flags> / -cflags=<flags>
Accepted for command compatibility; native linker flags are
applied by build rather than emit-ir.
Options for emit-js:
-wasm-mode <mode> / --wasm-mode=<mode>
Generate the normal or concurrent JavaScript runtime.
--alloc=region / -alloc=region
Compile the source with region mode enabled.
Options for build:
-target wabt
Build through WAT/WABT and emit a browser-oriented Wasm
module with automatically generated runtime.js.
-wasm-mode <mode> / --wasm-mode=<mode>
WebAssembly runtime mode: normal or concurrent.
--alloc=region / -alloc=region
Enable optional function-scoped region allocation.
-cflags <flags> / -cflags=<flags>
Extra flags passed directly to Clang.
-g Generate DWARF debug metadata and use a debug-friendly
native compilation configuration. For `-target wabt`, also
emit Wasm DWARF custom sections and a `.wasm.map` file.
--source-map-base <url> / --source-map-base=<url>
URL prefix for the embedded source-map URL and source URLs.
The map is written beside the output Wasm file.
--no-source-map Do not embed the `sourceMappingURL` custom section. The
`.wasm.map` file is still written for external use.
Options for run:
-wasm-mode <mode> / --wasm-mode=<mode>
Forward the normal or concurrent mode to the WASM build.
--alloc=region / -alloc=region
Forward region allocation mode to the build.
-cflags <flags> / -cflags=<flags>
Forward additional Clang flags to the build.
-g Forward debug information generation to the build.
Options for go:
-o <path> / -o=<path>
Output the aggregated `.syso` object path.
-target <name> / --target=<name>
Target platform or triple for the generated object.
-v / --verbose Enable verbose logging.
-vv / --vv Enable detailed logging.
Unknown non-option arguments are treated as source files or directories. The
`emit-ir`, `emit-js`, and `build` commands require at least one input source.
External module imports and hikec get dependency installation
Interface-valued AsyncIterable dispatch for for value := range <-stream
Optional function-scoped region allocation (--alloc=region)
Complete lexical lifetime inference and release handling for local string and slice values, including heap promotion for returned values and fixed-buffer reuse for repeated appends
Package registry and remote dependency resolution
Self-hosting compiler frontend in Hike for the currently covered test paths
Validate the self-hosted Hike compiler when built and executed as WebAssembly
The memory and concurrency models are intentionally explicit: area blocks, threadable variables, and concurrent variables are available today, while additional lifetime diagnostics and broader platform coverage remain future work.
MIT License
240 commits
Go
92.4%
LLVM
7.2%
A zero-overhead systems language uniting Go ergonomics with bare-metal performance: zero-GC, direct C-ABI, and ultra-lightweight, WASI-free WebAssembly (normal & concurrent).
Go
83
240 commits
updated Sep 25, 2026
hike-lang)A lightweight, zero-overhead systems programming language targeting Windows, Linux, and WebAssembly.
A systems programming language with Go-like syntax that compiles to LLVM IR or WebAssembly, generating C-ABI compliant shared libraries, standalone executables, and browser-ready Wasm modules.

Chrome DevTools showing source-level breakpoints, local variables, and a function return value while running Hike WebAssembly.
Build Hike with -g and debug the original .hike source instead of stepping
through generated LLVM IR, WAT, or JavaScript glue code. The same source-level
workflow is available for both native and WebAssembly targets:
# Native: debug with the usual GDB/LLDB or VS Code integration.
hikec build -target linux -g main.hike -o main
# WebAssembly: debug the original source in Chrome DevTools and place the
# source-map URL relative to the hosted Wasm files.
hikec build -target wabt -g --source-map-base ./ main.hike -o main.wasm
The WABT debug build writes main.wasm.map beside main.wasm and embeds a
sourceMappingURL custom section pointing to it. Set
--source-map-base=/assets/wasm when the Wasm and map files are served below a
different URL prefix. If DWARF should be the only browser debug mapping, use
--no-source-map; the map file is still generated, but its URL is not embedded
in the Wasm module.
The compiler emits DWARF information for native binaries and Wasm DWARF custom sections for the WABT target. This makes source-line breakpoints, stepping, and local-variable inspection available in the debugger that matches the target:
| Capability | Native build | WebAssembly build |
|---|---|---|
| Debug switch | -g | -g |
| Debug format | DWARF | DWARF in Wasm custom sections |
| Debugger | GDB, LLDB, or VS Code | Chrome DevTools |
| Source view | Original .hike files | Original .hike files in Sources |
| Inspection | Locals and return values | Locals and return values |
Hike keeps the execution model small: there is no garbage collector and no
always-on language scheduler. Runtime support is emitted only when the program
uses a runtime-backed feature. Debug information is likewise opt-in, so release
builds remain lean while -g builds provide the information needed for precise
source-level debugging.
For the browser-oriented WABT flow, Hike generates the Wasm module and the
required runtime.js integration as part of the build. No handwritten
JavaScript glue, Emscripten setup, wasm-bindgen packaging, or source-map
configuration is required to start debugging:
cd examples/browser
make build
make serve
Open the page in Chrome, press F12, and select the .hike source in the
Sources panel. Set a breakpoint on a Hike statement and inspect locals while
the Wasm code is running. The browser example demonstrates the complete flow,
including source-level stepping and return-value inspection.
Note: Hike is under active development. Full native builds and test suites are verified on Windows and Linux using Clang/LLVM, alongside browser/Node.js WebAssembly targets.
The compiler currently supports LLVM IR and WAT generation, native and wasm32/WABT builds, generic type/function specialization, closures with escape analysis, interfaces, collections, inline assembly, region allocation, threadable and concurrent module variables, and an automated native/WASM test suite. Recent implementation work has also established a length-aware string representation with shared substring views, reference counting, and copy-on-write mutation.
Parts of the test suite already use a Hike-implemented compiler as a self-hosted prerequisite. Native self-hosting is therefore exercised, while running the self-hosted Hike compiler after compiling it to WebAssembly still requires validation.
The language and memory designs are actively evolving. Region allocation and area allocation are explicit memory models, with compiler-wide lifetime inference for temporary and shared string buffers implemented alongside them. See the linked design documents below for the exact implementation boundaries.
| Document | Summary |
|---|---|
alloc-region.md | Region allocation, arena lifetime, escape promotion, diagnostics, examples, and limitations. |
area-allocation.md | Area blocks, scoped memory reuse, deep-copy requirements, and native/WASM behavior. |
encoding.md | UTF-8 rules, string and buffer layouts, shared substring views, reference counting, and copy-on-write. |
wasm.md | wasm32 target behavior, JavaScript runtime integration, exports, memory access, and testing. |
concurrency.md | Async tasks, channels, worker synchronization, closure transfer, and generated task bridges. |
thread-variables.md | Threadable and concurrent module variables, visibility, storage, and synchronization rules. |
external-module.md | External module declarations, hikec get, repository checkouts, and release source archives. |
eventloop.md | Event-loop abstractions built on channels, task invocation, and asynchronous result handling. |
build-constraints-and-assembly.md | Build constraints and the inline assembly syntax and lowering rules. |
without_cgo.md | C-ABI integration without cgo, .syso builds, and ownership rules at language boundaries. |
gpu/webgpu/Hike.md | WebGPU-oriented Hike integration and GPU programming notes. |
Hike is a lightweight systems programming language combining Go-style syntax and ergonomics with C-equivalent execution, direct C-ABI compatibility, and no garbage collection.
The Hike compiler (hikec) has two WebAssembly-capable backends. The LLVM
backend lowers Hike to LLVM IR (.ll), allowing Clang/LLVM to apply its
optimization pipeline before producing native binaries or optimized
wasm32-unknown-unknown modules. The WABT backend lowers directly to WAT, the
standard WebAssembly text representation, and uses wat2wasm to produce a
module without an LLVM round trip. This direct path is the foundation for
Hike's WebAssembly DWARF and browser-debugging support.
The compiler builds standalone executables, C-compatible shared libraries (.dll / .so), and WebAssembly modules (.wasm). When exporting library functions, hikec automatically generates corresponding C/C++ header files (.h).
:=), and generic type parameters..h headers for C/C++ host integration.for-range traversal using compile-time stack allocation (alloca).hike.mod declares dependencies with require, and hikec get downloads them into the project-local .hike/deps tree.wasm32 module, or WABT for direct WAT-to-Wasm generation and browser-oriented debugging.--alloc=region groups eligible function-local allocations into bump arenas and releases them in O(1) at the region boundary.area(...) { ... } provides explicit, thread-local scoped storage with bulk release at block exit; values that outlive the block must be copied explicitly.Async combine to make common multithreaded programs easier to express without requiring a garbage collector or an always-on scheduler.-target wabt -g.Hike supports function-level LLVM inline assembly through the __asm__ { ... }
construct. The first line inside the block declares the operand mapping. The
following entries are the assembly template, output constraints, input
constraints, and clobber constraints:
func encryptBlockFast(roundKeys *byte, dst *byte, src *byte) {
__asm__{
params: roundKeys, dst, src
"movups (%2), %xmm0\n",
"movups (%0), %xmm1\n",
"pxor %xmm1, %xmm0\n",
"movups %xmm0, (%1)\n",
"",
"r,r,r",
"~{xmm0},~{xmm1},~{memory}"
}
}
%0, %1, and %2 refer to the operands listed after params:. They are
translated to LLVM inline-assembly operand references; other assembly text is
preserved. Register names such as %xmm0 remain hardware register names.
Clobbers must declare registers and memory modified by the assembly. Native
instructions must be protected with build constraints such as amd64; a
portable implementation should be provided for targets such as wasm32.
The complete build-constraint and assembly rules are documented in
build-constraints-and-assembly.md.
[ .hike Source Code ]
│
▼ (hikec: Go Frontend, Typecheck, Monomorphization)
[ AST & Desugaring ]
├───► Auto-Generated C/C++ Header (.h) [Optional via -header]
├───► LLVM Backend ──► LLVM IR (.ll) ──► Clang/LLVM optimizer
│ ├──► Native executable/library
│ └──► Optimized wasm32 module
│
└───► WABT Backend ──► WAT ──► wat2wasm
└──► Wasm + DWARF/source mapping
└──► Chrome DevTools
Go: 1.21+
LLVM / Clang: 15.0+ (clang and lld in your PATH)
Windows Toolchain: MinGW-w64 GCC runtime (x86_64-w64-windows-gnu)
Make (MinGW / MSYS2 / Linux / macOS)
Python: 3.8+ (for integration test suites)
WABT: wat2wasm in your PATH when using -target wabt
Wasmtime: Used by the WASM test suites to execute generated WebAssembly modules
GDB or LLDB: Required for debugging native binaries built with DWARF debug information; Wasm DWARF debugging is supported through Chrome DevTools
git clone https://github.com/kanryu/hike-lang.git
cd hike-lang
# Run Unit Tests
go test ./...
# Build Compiler CLI Driver
go build -o hikec.exe ./cmd/hikec
Use --export-symbols to write a JSON catalogue of the identifiers found in
the loaded Hike program:
hikec emit-ir --export-symbols symbols.json main.hike
The catalogue contains imported modules, free functions, global variables,
local variables (including parameters), and interface-compatible methods
grouped under fixed_receivers with their receiver type. Built-in interface
capabilities are included in the same group. Structs and interfaces are
included with their members and method sets; structs, interfaces, members,
functions, and fixed receivers use package-qualified names.
Hike supports explicit type declarations and local type inference via :=. Primitive types map to fixed-width representations: int (i64), float64 (double), byte (u8), and bool (i1). The native string type is a length-aware fat value: { i8*, i32, i32 }, occupying 16 bytes on 64-bit targets and 12 bytes on wasm32.
package main
var globalCounter int = 0
const MaxLimit int = 1024
func DemoVariables() {
var a int = 42
var b float64 = 3.14159
var isEnabled bool = true
var msg string = "Hello, Hike!"
// Type inference
count := 100
ratio := 0.75
}
Structs follow C memory layouts without hidden metadata or GC headers. Raw pointer operations do not incur runtime tracking.
package main
type Point struct {
X float64
Y float64
}
type Rectangle struct {
TopLeft Point
BottomRight Point
}
func CreatePoint(x float64, y float64) Point {
return Point{X: x, Y: y}
}
// Pass by pointer to avoid copying
func OffsetPoint(p *Point, dx float64, dy float64) {
p.X = p.X + dx
p.Y = p.Y + dy
}
Functions are first-class constructs and support multiple return values.
package main
func SafeDivide(a int, b int) (int, bool) {
if b == 0 {
return 0, false
}
return a / b, true
}
func DemoFunctions() {
result, ok := SafeDivide(10, 2)
if ok {
// Use result
}
}
Hike supports if statements with short variable declarations, standard 3-clause for loops, for-range iterations, and switch statements.
package main
func DemoControlFlow(values [5]int) int {
sum := 0
// 1. If with initializer
if n := len(values); n > 0 {
sum = sum + 1
}
// 2. 3-clause for loop
for i := 0; i < 5; i = i + 1 {
sum = sum + values[i]
}
// 3. for-range loop over fixed arrays
for idx, val := range values {
if val < 0 {
continue
}
sum = sum + val
}
// 4. Switch statement
status := 200
switch status {
case 200:
sum = sum + 10
case 404, 500:
sum = sum - 1
default:
sum = 0
}
return sum
}
Fixed-size arrays allocate contiguous memory inline. Slices provide dynamic views backed by a three-word header: pointer, length, and capacity.
package main
func DemoArrays() {
// Fixed-size array
var arr [4]int
arr[0] = 10
arr[1] = 20
primes := [3]int{2, 3, 5}
// Slice expression
sub := primes[1:3]
}
Generic functions and struct definitions are specialized into concrete implementations at compile time, eliminating runtime dispatch overhead.
package main
// Generic function with type union constraint
func Min[T int | float64](a T, b T) T {
if a < b {
return a
}
return b
}
// Generic struct
type Pair[K, V] struct {
Key K
Value V
}
func DemoGenerics() {
minInt := Min(10, 20) // Specializes Min__int
minFloat := Min(3.14, 2.71) // Specializes Min__float64
p := Pair[string, int]{Key: "hike", Value: 1}
}
Hike also supports compile-time value parameters for generic types. A const generic declaration must contain at least one type parameter, and const parameters must appear after all type parameters. Const arguments are limited to immediate primitive values or other const-generic parameters; ordinary variables and expressions involving runtime values are rejected.
type Matrix[T, Rows uint, Cols uint] struct {
data [Rows * Cols]T
}
func MatrixValue() int {
var m Matrix[int, 8, 8]
return 8 * 8
}
Matrix[int, 8, 8] is materialized as a concrete compile-time
specialization. The dimensions are available to layout and indexing code, so
the resulting fixed-size matrix has no runtime dimension metadata.
const Rows uint = 8
const Cols uint = 8
var m Matrix[int, Rows, Cols]
See the generic transformation and interface tests for additional examples of type and const-parameter specialization.
std/maps) & Indexing SugarHike provides a generic hash map implementation (std/maps) with syntax sugar for indexing, membership testing, deletion, and for-range traversal.
package main
import "std/maps"
func printf(format string, ...) int
func main() int {
// Initialize map with initial bucket capacity
hmap := maps.New[string, int](8)
// Subscript assignment sugar
hmap["Tokyo"] = 1400
hmap["Osaka"] = 880
hmap["Nagoya"] = 230
// Comma-ok lookup idiom
if val, ok := hmap["Osaka"]; ok {
printf("Osaka population: %d\n", val)
}
// for-range traversal (stack-allocated iterator)
for city, population := range hmap {
printf(" - %s: %d\n", city, population)
}
// Removal and length query
delete(hmap, "Nagoya")
printf("Remaining entries: %d\n", len(hmap))
return 0
}
Any user-defined struct can implement the Map Behavior protocol to enable indexing syntax (obj[k], obj[k] = v, len(obj), delete(obj, k)) and for-range loops.
To eliminate heap allocations during for-range iterations over custom containers, Hike uses a 2-pass protocol:
Pass 1 (Size Probe): InitIterator(nil) returns the state buffer byte size.
Stack Allocation: The compiler issues an alloca instruction on the caller's stack frame.
Pass 2 (Initialization): InitIterator(buf) initializes the allocated state buffer in-place.
Iteration: Next(buf) runs on each step, returning pointers to the current key/value and a continuation flag.
package main
type Entry[K, V] struct {
Key K
Value V
}
type CustomDictionary[K, V] struct {
Entries []Entry[K, V]
}
func (d *CustomDictionary[K, V]) Set(key K, val V) {
for i := 0; i < len(d.Entries); i = i + 1 {
if d.Entries[i].Key == key {
d.Entries[i].Value = val
return
}
}
d.Entries = append(d.Entries, Entry[K, V]{Key: key, Value: val})
}
func (d *CustomDictionary[K, V]) Get(key K) (V, bool) {
for i := 0; i < len(d.Entries); i = i + 1 {
if d.Entries[i].Key == key {
return d.Entries[i].Value, true
}
}
var zero V
return zero, false
}
func (d *CustomDictionary[K, V]) Len() int {
return len(d.Entries)
}
func (d *CustomDictionary[K, V]) Delete(key K) {
for i := 0; i < len(d.Entries); i = i + 1 {
if d.Entries[i].Key == key {
d.Entries[i] = d.Entries[len(d.Entries)-1]
d.Entries = d.Entries[:len(d.Entries)-1]
return
}
}
}
// --- 2-Pass Iterator Methods ---
type DictIterator struct {
Index int
}
func (d *CustomDictionary[K, V]) InitIterator(buf *byte) int {
if buf == nil {
return 8 // sizeof(DictIterator)
}
it := (*DictIterator)(buf)
it.Index = 0
return 0
}
func (d *CustomDictionary[K, V]) Next(buf *byte) (*K, *V, bool) {
it := (*DictIterator)(buf)
if it.Index >= len(d.Entries) {
return nil, nil, false
}
entry := &d.Entries[it.Index]
it.Index = it.Index + 1
return &entry.Key, &entry.Value, true
}
func DemoMapBehavior() {
var dict CustomDictionary[string, int]
// Uses indexing sugar
dict["itemA"] = 100
dict["itemB"] = 200
// Traverses with zero heap allocation
for k, v := range dict {
// ...
}
}
Async schedules a closure for asynchronous execution and returns a typed one-shot task handle. The receive operator <- waits for completion and unpacks the return value or values.
task := Async(func() int {
return 40 + 2
})
result := <-task
Channels are typed synchronization queues. They can be used directly for producer/consumer pipelines, including staged streaming downloads:
type Download struct {
Blocks chan int
}
func (d *Download) InitIterator(buf *byte) int {
return 0
}
func (d *Download) NextChannel(buf *byte) (chan int, bool) {
return d.Blocks, true
}
func download() *Download {
result := &Download{Blocks: make(chan int, 3)}
Async(func() int {
result.Blocks <- 10
result.Blocks <- 20
result.Blocks <- 30
return 0
})
return result
}
func consume() int {
total := 0
stream := download()
for block := range <-stream {
total = total + block
}
return total
}
The InitIterator and NextChannel method pair is the AsyncIterable[T] protocol defined in std/collections. for value := range <-stream receives blocks in channel order and can stop early with break. Both concrete streams and interface-typed streams are lowered through their respective method dispatch paths, including the concurrent WebAssembly runtime.
For memory-heavy or highly concurrent code, combine this protocol with the
memory models described in area-allocation.md and
thread-variables.md. Area blocks provide explicit
scoped storage, threadable variables avoid cross-worker sharing, and concurrent
variables provide synchronized shared state.
Functions can be passed as values, returned from factories, or defined inline as closures.
Reference Capturing: Captured variables maintain reference semantics across calls.
Automatic Heap Promotion: Variables that escape their stack scope (such as parameters returned inside a closure) are automatically promoted to heap allocation (malloc).
Fat Pointer ABI: Function values compile to a two-word structure:
$$\text{FuncValue} \implies { \text{i8* fn_ptr},, \text{i8* env_ptr} }$$
Top-level and stateless functions carry env_ptr = null. Closures carry a pointer to the captured environment.
package main
// 'base' is promoted to heap by escape analysis
func makeAdder(base int) func(int) int {
return func(n int) int {
return base + n
}
}
func main() int {
add100 := makeAdder(100)
result := add100(42) // => 142
counter := 0
increment := func() int {
counter = counter + 1 // Mutates outer variable
return counter
}
increment()
increment()
finalCount := increment() // finalCount == 3
return 0
}
Any top-level function taking POD types or pointers can be exported to C-ABI. Passing -header <name.h> instructs hikec to emit a C/C++ header matching the exported functions.
libcalc.hike)package main
type Vector2D struct {
X float64
Y float64
}
func Add[T int | float64](a T, b T) T {
return a + b
}
func HikeAddInt(a int, b int) int {
return Add(a, b)
}
func HikeAddFloat(a float64, b float64) float64 {
return Add(a, b)
}
func HikeDotProduct(v1 *Vector2D, v2 *Vector2D) float64 {
return (v1.X * v2.X) + (v1.Y * v2.Y)
}
# 1. Emit LLVM IR and C/C++ header
hikec emit-ir -header libcalc.h -o libcalc.ll libcalc.hike
# 2. Build shared library and import library using Clang
clang -shared -O3 -Wl,--export-all-symbols -Wl,--out-implib,libcalc.dll.a libcalc.ll -o libcalc.dll
libcalc.h)#ifndef HIKE_LIBCALC_H
#define HIKE_LIBCALC_H
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifndef HIKE_API
#if defined(_WIN32) || defined(__CYGWIN__)
#define HIKE_API __declspec(dllimport)
#else
#define HIKE_API extern
#endif
#endif
typedef struct Vector2D {
double X;
double Y;
} Vector2D;
HIKE_API int64_t HikeAddInt(int64_t a, int64_t b);
HIKE_API double HikeAddFloat(double a, double b);
HIKE_API double HikeDotProduct(Vector2D* v1, Vector2D* v2);
#ifdef __cplusplus
}
#endif
#endif
main.cpp)#include <iostream>
#include "libcalc.h"
int main() {
int64_t sumInt = HikeAddInt(400, 600);
double sumFloat = HikeAddFloat(1.414, 1.732);
Vector2D v1 = { 3.0, 4.0 };
Vector2D v2 = { 2.0, 5.0 };
double dot = HikeDotProduct(&v1, &v2);
std::cout << "SumInt: " << sumInt << std::endl;
std::cout << "Dot: " << dot << std::endl;
return 0;
}
Compile and link C++ directly:
clang++ -O3 main.cpp libcalc.dll.a -o client.exe
./client.exe
Unlike Go, which requires cgo, preambles, wrapper generation, and runtime stack-switching overhead, Hike interacts with C at zero runtime cost.
Because hikec compiles directly to standard LLVM IR and delegates code generation to Clang, a function declaration without a body (func ...) is emitted as a standard external C-ABI symbol declaration (declare).
You can call standard C library functions simply by declaring their signatures in your .hike source. The Clang backend automatically links the C runtime (libc / MinGW-w64 / MSVCRT) and resolves the symbols directly.
package main
// Declare standard C library functions directly
func printf(format string, ...) int
func puts(str string) int
func main() int {
puts("Hello directly from C libc!")
printf("Formatted number: %d\n", 42)
return 0
}
To integrate third-party C libraries (e.g., SQLite, Raylib, OpenSSL):
Declare the C API: Write matching function signatures without bodies in your Hike code.
Link via Clang: Pass -l<lib> or direct paths to static (.a / .lib) or dynamic (.so / .dll / .dylib) libraries during build.
main.hike)package main
// External library declaration (e.g., libcurl or a custom C library)
func my_c_library_init() int
func my_c_calculate(a int, b int) int
func main() int {
if my_c_library_init() != 0 {
return 1
}
res := my_c_calculate(10, 20)
return 0
}
# 1. Emit LLVM IR
hikec emit-ir -o main.ll main.hike
# 2. Compile and link external C library with Clang
clang -O3 main.ll -L/path/to/libs -lmy_c_library -o app.exe
hike.mod)Hike resolves local dependencies and package roots via hike.mod in the project root.
External Hike packages can be imported by declaring a require directive and
running hikec get. The command downloads the requested project into
.hike/deps, after which its packages are available through normal Hike
imports.
module my-project
hike 0.1.0
require github.com/kanryu/hike-gpu-webgpu v0.1.0
hikec get
When a package should be resolved from a directory relative to the project,
replace can be used. The path is resolved from the project module root; it
does not download or manage an external dependency:
replace github.com/kanryu/hike-gpu-webgpu => ../../gpu/webgpu
Use require with hikec get for project-managed external dependencies, and
use replace when a package should be resolved through a project-relative
directory.
For repository checkouts, release source archives, and dependency refresh
behavior, see external-module.md.
std/slices)The standard library provides generic collection operations for slices, conforming to standard functional and Go-like semantics. All operations are monomorphized at compile time with zero overhead.
Filter[T](s []T, predicate func(item T) bool) []T: Returns a new slice containing elements that satisfy the predicate.Map[T, U](s []T, transform func(item T) U) []U: Returns a new slice where each element is mapped via transform.IndexFunc[T](s []T, predicate func(item T) bool) int: Returns the index of the first element satisfying the predicate, or -1 if not found.Find[T](s []T, predicate func(item T) bool) (T, bool): Returns the first matching element and a boolean flag indicating success.SortFunc[T](s []T, cmp func(a T, b T) int): Performs an in-place quicksort using a three-way comparison function (a < b returns negative, a == b returns 0, a > b returns positive).SortBy[T](s []T, less func(a T, b T) bool): Performs an in-place quicksort using a boolean comparison predicate (a < b returns true).package main
import (
"std/slices"
)
func printf(format string, ...) int
func main() int {
nums := []int{5, 2, 8, 1, 9, 4}
// 1. Filter elements
evens := slices.Filter[int](nums, func(x int) bool {
return x % 2 == 0
})
// 2. Map elements
mapped := slices.Map[int, int](evens, func(x int) int {
return x * 10
})
// 3. Search elements
val, ok := slices.Find[int](nums, func(x int) bool {
return x > 5
})
idx := slices.IndexFunc[int](nums, func(x int) bool {
return x > 5
})
printf("Find: %d (ok: %d), Index: %d\n", val, ok, idx)
// 4. In-place sorting
slices.SortFunc[int](nums, func(a int, b int) int {
return a - b // Ascending
})
slices.SortBy[int](nums, func(a int, b int) bool {
return a > b // Descending
})
return 0
}
std/encoding/json)The standard library provides DOM parsing, traversal, mutation, serialization, and file I/O.
package main
import (
"std/encoding/json"
)
func printf(format string, ...) int
func main() int {
// 1. Read file content
content := json.ReadFile("data.json")
if len(content) == 0 {
printf("Failed to read data.json\n")
return 1
}
// 2. Parse DOM tree
doc := json.Parse(content)
if doc == nil {
printf("Failed to parse JSON\n")
return 1
}
// 3. Access fields
nameVal := doc.Get("name")
verVal := doc.Get("version")
if nameVal != nil {
printf("Name: %s\n", nameVal.AsString())
}
if verVal != nil {
printf("Version: %d\n", verVal.AsInt())
}
// 4. Mutate DOM
doc.Set("modified_by", json.NewString("hikec"))
newStats := json.NewObject()
newStats.Set("active_threads", json.NewNumber(8.0))
doc.Set("stats", newStats)
// 5. Serialize and write back
outStr := json.Stringify(doc)
json.WriteFile("output.json", outStr)
return 0
}
Hike provides two WebAssembly build paths. Choose the backend with
-target:
| Target | Backend | Best for |
|---|---|---|
wasm32 | LLVM/Clang | Optimized, compact, high-performance Wasm modules |
wabt | Direct WAT + wat2wasm | Source-level debugging in browser DevTools |
The wasm32 target lowers through LLVM IR, allowing the LLVM/Clang optimizer
to produce a compact and fast WebAssembly module. The wabt target emits the
standard WebAssembly text representation directly and converts it with WABT.
Because this path keeps the source-to-Wasm mapping under Hike's control, it is
the recommended backend for debugging Hike source in Chrome DevTools. Hike will
continue to strengthen the WABT backend as its primary direct-WebAssembly
pipeline.
Both backends emit the internal runtime support needed by the program and can generate the JavaScript runtime for the selected host mode. The generated Wasm modules are intended to run across WebAssembly hosts including browsers, Node.js, and other runtimes that provide the standard WebAssembly JavaScript API.
The generated WASM modules are intended to run across WebAssembly host
environments, including web browsers, Node.js, and other WASM runtimes that
provide the standard WebAssembly JavaScript API. In a web browser, a module
can be used immediately by referencing the generated runtime.js and the
.wasm binary from an HTML page; the runtime handles instantiation and exposes
the Hike exports to the page's JavaScript.
# Optimized Wasm through LLVM/Clang.
hikec build -target wasm32 -o main.wasm main.hike
# Direct WAT/WABT build for Chrome DevTools source debugging.
hikec build -target wabt -g --source-map-base ./ -o main.wasm main.hike
# The same host model can be exercised through Node.js.
hikec run -target wasm32 main.hike
build produces the .wasm module and automatically writes runtime.js in
the output directory. A WABT debug build also writes main.wasm.map and
embeds its sourceMappingURL custom section unless --no-source-map is
specified. run builds a temporary module and starts Node.js with the
generated runtime. This is the same host model used by the automated WASM
tests.
Use -wasm-mode normal for ordinary exported functions and
-wasm-mode concurrent when the program uses asynchronous tasks, workers, or
directed JavaScript callbacks:
hikec build -target wasm32 -wasm-mode normal -o app.wasm main.hike
hikec build -target wasm32 -wasm-mode concurrent -o app.wasm main.hike
hikec emit-js -target wasm32 -wasm-mode concurrent -o runtime.js main.hike
The generated runtime is reusable from both browsers and Node.js. Browser code
can instantiate the module and call exported cfunc functions in response to
events. Node.js tests load the same generated runtime with require or ES
module import and invoke the exports directly. Each WASM test keeps its
JavaScript driver separate while sharing the runtime implementation.
WASM host functions receive string data through the generated ABI bridge. Hike code can expose byte-pointer and length parameters when a direct host buffer interface is preferred, then construct a Hike string from that pair:
cfunc TransformString(input *byte, length int) cstring {
value := string(input, length)
return cstring(value + " [wasm]")
}
The runtime provides UTF-8 encoding/decoding helpers and access to linear
memory. Hike int values use WebAssembly i64 and should be passed from
JavaScript as BigInt; int32, byte, and bool use i32-compatible values.
Hike strings are length-aware fat values internally, while C-facing exports
are adapted to pointer-based ABI values by the compiler.
import { HikeRuntime } from "./runtime.js";
import fs from "node:fs/promises";
const runtime = new HikeRuntime();
const wasm = await runtime.load("main.wasm");
const result = wasm.AddNumbers(1234, 5678);
console.log(result);
For browser integration, instantiate the generated runtime after loading the
.wasm asset and call exported functions from event handlers. For Node.js,
the tests/wasm suite provides examples covering maps, strings, concurrent
workers, and JavaScript functions (jfunc).
The full host API, memory model, and integration examples are documented in
wasm.md.
Passing -g instructs hikec to emit LLVM DWARF metadata, enabling source-level breakpoints, single-stepping, and variable inspection in VS Code using GDB or LLDB.
.vscode/tasks.json{
"version": "2.0.0",
"tasks": [
{
"label": "Build Hike Debug Executable",
"type": "shell",
"command": "hikec ${file} -g -o${fileDirname}/main.ll && clang -g -O0 ${fileDirname}/main.ll -o${fileDirname}/app.exe",
"options": {
"cwd": "${fileDirname}"
},
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": ["$gcc"]
}
]
}
.vscode/launch.json{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Hike Program (F5)",
"type": "cppdbg",
"request": "launch",
"program": "${fileDirname}/app.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${fileDirname}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"miDebuggerPath": "gdb",
"preLaunchTask": "Build Hike Debug Executable",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
]
}
Pressing F5 compiles the active .hike file and launches the debug session, supporting breakpoints, Step Over (F10), Step Into (F11), and variable inspection.
hikec)Usage: hikec <command> [options] <source.hike... | directory>
Commands:
go Compile a directory of .go.hike files into one .syso object
get Download modules listed in hike.mod into .hike/deps
emit-ir Generate target LLVM IR (.ll) (default for a source input)
emit-js Generate the WebAssembly JavaScript runtime.js bridge
build Compile Hike source into a native or WebAssembly binary via Clang
run Build and immediately execute a native or WebAssembly program
Common options:
-o <path> / -o=<path>
Output path. For emit-ir this is .ll; for emit-js it is
runtime.js; for build it is the executable or .wasm.
-target <name> / --target=<name>
Target triple or shorthand: windows, windows-msvc, linux,
darwin, wasm32, wasm64, or wabt.
-v / --verbose Enable verbose logging.
-vv / --vv Enable detailed instruction-level logging.
Options for emit-ir:
-header <path> / --header=<path>
Generate a C/C++ header for exported declarations.
-wasm-mode <mode> / --wasm-mode=<mode>
WebAssembly runtime mode: normal or concurrent.
--alloc=region / -alloc=region
Enable optional function-scoped region allocation.
-cflags <flags> / -cflags=<flags>
Accepted for command compatibility; native linker flags are
applied by build rather than emit-ir.
Options for emit-js:
-wasm-mode <mode> / --wasm-mode=<mode>
Generate the normal or concurrent JavaScript runtime.
--alloc=region / -alloc=region
Compile the source with region mode enabled.
Options for build:
-target wabt
Build through WAT/WABT and emit a browser-oriented Wasm
module with automatically generated runtime.js.
-wasm-mode <mode> / --wasm-mode=<mode>
WebAssembly runtime mode: normal or concurrent.
--alloc=region / -alloc=region
Enable optional function-scoped region allocation.
-cflags <flags> / -cflags=<flags>
Extra flags passed directly to Clang.
-g Generate DWARF debug metadata and use a debug-friendly
native compilation configuration. For `-target wabt`, also
emit Wasm DWARF custom sections and a `.wasm.map` file.
--source-map-base <url> / --source-map-base=<url>
URL prefix for the embedded source-map URL and source URLs.
The map is written beside the output Wasm file.
--no-source-map Do not embed the `sourceMappingURL` custom section. The
`.wasm.map` file is still written for external use.
Options for run:
-wasm-mode <mode> / --wasm-mode=<mode>
Forward the normal or concurrent mode to the WASM build.
--alloc=region / -alloc=region
Forward region allocation mode to the build.
-cflags <flags> / -cflags=<flags>
Forward additional Clang flags to the build.
-g Forward debug information generation to the build.
Options for go:
-o <path> / -o=<path>
Output the aggregated `.syso` object path.
-target <name> / --target=<name>
Target platform or triple for the generated object.
-v / --verbose Enable verbose logging.
-vv / --vv Enable detailed logging.
Unknown non-option arguments are treated as source files or directories. The
`emit-ir`, `emit-js`, and `build` commands require at least one input source.
External module imports and hikec get dependency installation
Interface-valued AsyncIterable dispatch for for value := range <-stream
Optional function-scoped region allocation (--alloc=region)
Complete lexical lifetime inference and release handling for local string and slice values, including heap promotion for returned values and fixed-buffer reuse for repeated appends
Package registry and remote dependency resolution
Self-hosting compiler frontend in Hike for the currently covered test paths
Validate the self-hosted Hike compiler when built and executed as WebAssembly
The memory and concurrency models are intentionally explicit: area blocks, threadable variables, and concurrent variables are available today, while additional lifetime diagnostics and broader platform coverage remain future work.
MIT License
240 commits
Go
92.4%
LLVM
7.2%