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.
With a minimal C wrapper, Hike can also orchestrate GPGPU and AI programs using APIs such as OpenCL and ONNX Runtime while keeping buffer management, tensor preparation, and application logic in Hike.
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.wasmThe 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 serveOpen 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. |
compact-dict.md |
Compact-dict map[K]V, legacy hashmap[K]V, shared string hashing, and iteration behavior. |
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_AS_BETTER_C.md |
How Hike combines C-level control with concise GPGPU and AI integration, including OpenCL and ONNX Runtime examples. |
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).
- Go-Inspired Ergonomics: Multi-return values, slices, structs, type inference (
:=), and generic type parameters. - Zero Runtime Overhead: No GC pauses, no always-on language scheduler, and standard C memory layout. Runtime support is emitted as internal backend functions, so unused facilities can be eliminated from the final binary; programs that do not use runtime-backed features need no runtime code.
- Compile-Time Monomorphization: Generic functions and types are fully specialized during compilation without dynamic dispatch penalties.
- First-Class C-ABI Support: Emits pure C-ABI binaries and automatically emits matching
.hheaders for C/C++ host integration. - 2-Pass Stack Iterators: Custom containers can provide zero-allocation
for-rangetraversal using compile-time stack allocation (alloca). - Closures with Escape Analysis: Lexical closures capture by reference. Variables escaping their stack lifetime are promoted to the heap, unified under a 2-word fat pointer ABI.
- Built-in Module Management:
hike.moddeclares dependencies withrequire, andhikec getdownloads them into the project-local.hike/depstree. - Dual WebAssembly Backends: Use LLVM/Clang for an optimized
wasm32module, or WABT for direct WAT-to-Wasm generation and browser-oriented debugging. - Optional Region Allocation:
--alloc=regiongroups eligible function-local allocations into bump arenas and releases them in O(1) at the region boundary. - Scoped Area Memory:
area(...) { ... }provides explicit, thread-local scoped storage with bulk release at block exit; values that outlive the block must be copied explicitly. - Threadable Module Variables: Threadable variables are isolated per worker thread, while concurrent variables provide language-level atomic or locked access across workers.
- Practical Multithreading: Area memory, threadable module variables, concurrent module variables, channels, and
Asynccombine to make common multithreaded programs easier to express without requiring a garbage collector or an always-on scheduler. - Length-Aware Strings: Native strings use a fat representation with a backing pointer, byte offset, and byte length; substring views share storage and writes use copy-on-write when necessary.
- Source-Level DWARF Debugging: Generates debug metadata for VS Code, GDB, and LLDB, plus Wasm DWARF custom sections for Chrome DevTools when using
-target wabt -g.
[ .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+ (
clangandlldin yourPATH) -
Windows Toolchain: MinGW-w64 GCC runtime (
x86_64-w64-windows-gnu) -
Make (MinGW / MSYS2 / Linux / macOS)
-
Python: 3.8+ (for integration test suites)
-
WABT:
wat2wasmin yourPATHwhen 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.hikeThe 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.
If you already know Go, the core surface syntax is intentionally familiar:
variables, structs, slices, multiple returns, :=, if, for, for-range,
and switch work in the expected style. The important differences begin at
the memory, ABI, and compilation boundaries.
package main
type Point struct { X, Y float64 }
func Divide(a, b int) (int, bool) {
if b == 0 { return 0, false }
return a / b, true
}
func main() int {
pt := Point{X: 1.0, Y: 2.0}
vals := []int{10, 20, 30}
total := 0
for idx, v := range vals {
total = total + idx + v
}
_, _ = pt, total
return 0
}The rest of this section focuses on the parts that make Hike different from Go: predictable memory ownership, compile-time specialization, direct C ABI boundaries, and backend-level observability.
cbuffer is a built-in type for a native byte buffer returned by C libraries
and other foreign-function interfaces. It combines the native data pointer
and byte length with a release callback, without converting the allocation into
an ordinary Hike []byte or guessing which allocator should free it.
extern func native_alloc(size int) *byte
extern func native_free(memory *byte)
memory := native_alloc(size)
release := func() { native_free(memory) }
buffer := cbuffer(memory, size, release)
defer buffer.Close()
first := buffer[0]
nativePointer := buffer.Ptr()
length := buffer.Len()
cbuffer does not copy the data and does not participate in Hike slice
reference counting. The owner must call Close; closing is idempotent and
invokes the stored allocator-specific release callback exactly once. A range
expression such as buffer[1:3] creates an independent Hike-owned []byte
copy. Never pass a borrowed native pointer to cbuffer unless the release
callback and lifetime are genuinely owned by the resulting value.
The complete ownership, callback, slicing, and SQLite examples are documented
in cbuffer.md.
cstring is a separate built-in type for a C char*: a pointer to a
NUL-terminated byte sequence. A C char* parameter or variable can therefore
be represented directly as cstring, while Hike's ordinary string remains a
length-aware UTF-8 value. C-string values can use the built-in len function,
and can be converted to or from Hike strings at an explicit boundary.
func printf(format cstring, ...) int
func PrintCString(p *byte) int {
text := cstring(p)
return len(text)
}For a non-NUL-terminated buffer, keep the value as *byte plus a length or
construct a cstring with an explicit length-aware conversion. See
encoding.md for the exact representation and conversion
rules.
Use area(...) { ... } for scoped storage with bulk release, or
--alloc=region for eligible function-local allocations grouped into a
region and released at the region boundary. Values that must survive the
area must be copied explicitly with deepcopy.
func ExtractTail(input []byte) []byte {
var result []byte
area(64) {
// A large working byte slice is allocated inside the area.
working := make([]byte, 48*1024)
copy(working, input)
// Keep only the tail after the area by making an explicit deep copy.
tail := working[len(working)-1024:]
result = deepcopy(tail)
}
// All allocations made in the area, including the large working buffer,
// have already been released together when the area ended; only result
// remains valid here.
return result
}
See area-allocation.md and
alloc-region.md.
Custom containers can implement a size-probe plus initialization protocol.
Hike then reserves the iterator state with alloca, keeping for-range
traversal off the heap. The iterator protocol and examples are documented in
area-allocation.md and the compiler tests.
The same custom container can implement the Map Behavior methods Set, Get,
Len, and Delete to enable indexing syntax such as dict[key],
dict[key] = value, len(dict), delete(dict, key), and for-range traversal.
The protocol has two passes:
InitIterator(nil)returns the number of bytes required for iterator state.- The caller allocates that state on its stack with
alloca. InitIterator(buf)initializes the state buffer in place.Next(buf)returns pointers to the current key and value plus a continuation flag.
type Entry[K, V] struct {
Key K
Value V
}
type CustomDictionary[K, V] struct {
Entries []Entry[K, V]
}
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 SumValues(dict *CustomDictionary[string, int]) int {
total := 0
for _, value := range dict {
total = total + value
}
return total
}
The iterator state is temporary caller-owned storage. The iteration itself does not allocate a heap object for the iterator, while the container's own entries retain their normal lifetime and ownership rules.
Value parameters such as Rows and Cols can participate in compile-time
type specialization. The resulting layout and indexing operations carry no
runtime dimension metadata when the values are compile-time constants.
type Matrix[T, Rows uint, Cols uint] struct {
data [Rows * Cols]T
}
var m Matrix[int, 8, 8]A function declaration without a body is emitted as an external C-ABI symbol; cgo is not required for a direct declaration such as:
func printf(format string, ...) intSee Direct C Interoperability
for linking and ownership rules.
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 := <-taskChannels 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:
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
}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.
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.
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
#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#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.
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 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 getWhen 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.
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 viatransform.IndexFunc[T](s []T, predicate func(item T) bool) int: Returns the index of the first element satisfying the predicate, or-1if 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 < breturns negative,a == breturns 0,a > breturns positive).SortBy[T](s []T, less func(a T, b T) bool): Performs an in-place quicksort using a boolean comparison predicate (a < breturns 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
}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 host environments,
including web browsers, Node.js, and other 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.hikebuild 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.hikeThe 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.
{
"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"]
}
]
}
{
"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.
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 getdependency installation -
Interface-valued
AsyncIterabledispatch forfor 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
