1DOLLY 72PACKAGE mojo3REQUIRES HOST runtime@045# The Mojo compiler, compiled and linked inside Dolly from the sources upstream6# builds with Bazel, the standard library and the package gpu it precompiles,7# and what a program links against: mojo build NAME.mojo makes a Dolly8# executable.9FROM /v0.1.4/demos/mojo/Dollyfile-mojo-llvm 87f031c31c024255d664a5a584c23a5bcc759aced3e5892b300f7a28e390109d10# mojo build links what it compiles with the C toolchain.11INSTALL /v0.1.4/Dollyfile-libdolly-gpu 1308d574fd8b030fac77cf9814dc9925d2737be130a6401b9929c80e13307bf212INSTALL /v0.1.4/Dollyfile-cc dadb1af6ae3450a3a48bf4c955fe40d012a9a20d6dc3eca63c4f2737ab4080cc13REQUIRES HEADER gpu14REQUIRES LIB    dolly-gpu15REQUIRES TOOL cmake16REQUIRES TOOL make17REQUIRES TOOL python18REQUIRES TOOL cc19REQUIRES TOOL c++20REQUIRES TOOL ar21REQUIRES TOOL gzip22REQUIRES TOOL tar23REQUIRES TOOL patch24REQUIRES TOOL time25REQUIRES TOOL cp26REQUIRES TOOL mkdir27REQUIRES TOOL rm28REQUIRES TOOL sha256sum29REQUIRES TOOL cut3031SOURCE /v0.1.4/dist/static/mojo/mojo.tar.gz 85c382a809ee195c5d153de052aef38618ef1466d9f54a603a2e486445fda9fd /tmp/mojo.tar.gz32SLOP slop -c 'gzip -dc /tmp/mojo.tar.gz | tar -xf - -C /'33# Where the sources name Linux, macOS and Windows, and Dolly is none of them.34SLOP patch -p1 -d /tmp/mojo/modular -i /tmp/mojo/mojo-dolly.patch35# The targets a Dolly program and a WebGPU kernel are compiled for, and the36# standard library's bindings to Dolly's libc (target/README.md).37SLOP patch -p1 -d /tmp/mojo/modular -i /tmp/mojo/target/patches/compiler.patch38SLOP patch -p1 -d /tmp/mojo/modular -i /tmp/mojo/target/patches/stdlib.patch39# The build graph of //Mojo/tools/mojo:mojo, from upstream's BUILD.bazel files.40SLOP slop -c 'python /tmp/mojo/bazel-to-cmake.py /tmp/mojo/modular > /tmp/mojo/CMakeLists.txt'4142# As mojo-llvm compiles LLVM. The version is the tag's, without Bazel's43# development label. The data of LLVM and MLIR and an 8 MiB stack need more44# than the 16 MiB a process starts with.45FILE /tmp/mojo/Dolly.cmake46    set(CMAKE_BUILD_TYPE Release CACHE STRING "")47    set(CMAKE_C_FLAGS "-std=gnu17" CACHE STRING "")48    set(CMAKE_CXX_FLAGS "-include endian.h -DCLANG_BUILD_STATIC" CACHE STRING "")49    set(CMAKE_EXE_LINKER_FLAGS "-Wl,--initial-memory=67108864" CACHE STRING "")50    set(CMAKE_RULE_MESSAGES OFF CACHE BOOL "")51    set(MOJO_SOURCE /tmp/mojo/modular CACHE PATH "")52    set(MOJO_PORT /tmp/mojo/port CACHE PATH "")53    set(MOJO_LLVM /opt/mojo-llvm CACHE PATH "")54    set(FMT /tmp/mojo/fmt CACHE PATH "")55    set(XXHASH /tmp/mojo/xxhash CACHE PATH "")56    set(OPENTELEMETRY /tmp/mojo/opentelemetry CACHE PATH "")57    set(MOJO_VERSION_LABEL "" CACHE STRING "")58# The revision is the pin and this archive: the compiler keys its caches by59# it, where upstream reads the build id of its own ELF file.60SLOP time slop -c 'cmake -C /tmp/mojo/Dolly.cmake -DMODULAR_VERSION_REVISION=c6fa49f7+dolly.$(sha256sum /tmp/mojo.tar.gz | cut -c1-8) -S /tmp/mojo -B /tmp/mojo/build'6162# 356 units and 134 TableGen runs. Three jobs: the units that include every63# dialect take 1.9 GB each.64SLOP CWD /tmp/mojo/build time make -k -j3 Mojo.tools.mojo.mojo65SLOP mkdir -p /usr/lib/mojo /usr/share/licenses/mojo66SLOP cp /tmp/mojo/build/mojo /usr/bin/mojo67SLOP /usr/bin/mojo --version68# The standard library and the package gpu, precompiled by this compiler.69SLOP time /usr/bin/mojo precompile -o /usr/lib/mojo/std.mojoc /tmp/mojo/modular/Mojo/stdlib/std70SLOP time /usr/bin/mojo precompile -I /usr/lib/mojo -o /usr/lib/mojo/gpu.mojoc /tmp/mojo/target/gpu71# What the executable is linked from.72SLOP cp /tmp/mojo/modular/LICENSE /usr/share/licenses/mojo/LICENSE73SLOP cp /opt/mojo-llvm/LICENSE.TXT /usr/share/licenses/mojo/LICENSE.llvm74SLOP cp /opt/mojo-llvm/LICENSE.zstd /usr/share/licenses/mojo/LICENSE.zstd75SLOP cp /tmp/mojo/fmt/LICENSE /usr/share/licenses/mojo/LICENSE.fmt76SLOP cp /tmp/mojo/xxhash/LICENSE /usr/share/licenses/mojo/LICENSE.xxhash77SLOP cp /tmp/mojo/opentelemetry/LICENSE /usr/share/licenses/mojo/LICENSE.opentelemetry7879# What a Mojo object links against in Dolly: cc NAME.o -lmojo-rt, and80# -lmojo-gpu -ldolly-gpu for a program that uses the package gpu.81FILE /tmp/mojo-runtime/runtime.c82    /* The compiler runtime of a Mojo program in Dolly: the KGEN_CompilerRT_*83     * functions that the standard library and the compiler's output call.84     *85     * Upstream's is Mojo/lib/CompilerRT, over AsyncRT's thread pool, tcmalloc,86     * Support and LLVMSupport. This one is its interface over libc for one87     * thread: a task runs when something waits for it, and the parallelism level88     * is 1. A string argument is a pointer and a length, as the compiler emits a89     * two-word struct for wasm64. Python interoperation is absent, so a program90     * that uses it does not link. */91    #include <stdarg.h>92    #include <stdbool.h>93    #include <stdint.h>94    #include <stdio.h>95    #include <stdlib.h>96    #include <string.h>97    #include <time.h>98    99    /* Memory */100    101    void *KGEN_CompilerRT_AlignedAlloc(int64_t alignment, int64_t size) {102      void *memory = NULL;103      if (alignment <= 0) alignment = 16;104      if (alignment < (int64_t)sizeof(void *)) alignment = sizeof(void *);105      return posix_memalign(&memory, (size_t)alignment, size > 0 ? (size_t)size : 1) ? NULL : memory;106    }107    108    void KGEN_CompilerRT_AlignedFree(void *memory) { free(memory); }109    110    /* Globals, destroyed latest first */111    112    struct global {113      struct global *next;114      size_t length;115      void *value;116      void (*destroy)(void *);117      char name[];118    };119    120    enum { INDEXED_GLOBALS = 3 };121    static struct global *globals;122    static struct { void *value; void (*destroy)(void *); } indexed[INDEXED_GLOBALS];123    124    void *KGEN_CompilerRT_GetOrCreateGlobal(const char *name, size_t length, void *(*create)(void),125                                            void (*destroy)(void *)) {126      for (struct global *entry = globals; entry; entry = entry->next) {127        if (entry->length == length && !memcmp(entry->name, name, length)) return entry->value;128      }129      if (!create) return NULL;130      void *value = create();131      struct global *entry = malloc(sizeof *entry + length);132      if (!entry) abort();133      *entry = (struct global){globals, length, value, destroy};134      memcpy(entry->name, name, length);135      globals = entry;136      return value;137    }138    139    void *KGEN_CompilerRT_GetGlobalOrNull(const char *name, size_t length) {140      return KGEN_CompilerRT_GetOrCreateGlobal(name, length, NULL, NULL);141    }142    143    static void *inserted;144    static void *take_inserted(void) { return inserted; }145    146    void KGEN_CompilerRT_InsertGlobal(const char *name, size_t length, void *value) {147      inserted = value;148      KGEN_CompilerRT_GetOrCreateGlobal(name, length, take_inserted, NULL);149    }150    151    void *KGEN_CompilerRT_GetOrCreateGlobalIndexed(size_t index, void *(*create)(void), void (*destroy)(void *)) {152      if (index >= INDEXED_GLOBALS) abort();153      if (!indexed[index].value) {154        indexed[index].value = create();155        indexed[index].destroy = destroy;156      }157      return indexed[index].value;158    }159    160    void KGEN_CompilerRT_DestroyGlobals(void) {161      while (globals) {162        struct global *entry = globals;163        globals = entry->next;164        if (entry->destroy && entry->value) entry->destroy(entry->value);165        free(entry);166      }167      for (size_t index = 0; index < INDEXED_GLOBALS; index++) {168        void *value = indexed[index].value;169        indexed[index].value = NULL;170        if (value && indexed[index].destroy) indexed[index].destroy(value);171      }172    }173    174    /* Clang names a C program's main(argc, argv) __main_argc_argv for Wasm, and175     * libc calls that. Mojo names its entry point main. */176    int mojo_main(int, char **) __asm__("main");177    int __main_argc_argv(int count, char **values) { return mojo_main(count, values); }178    179    /* Arguments */180    181    struct string { const char *bytes; size_t length; };182    struct argument_list { struct string *list; size_t count; };183    static struct string no_arguments = {"", 0}, *arguments = &no_arguments;184    static size_t argument_count = 1;185    186    void KGEN_CompilerRT_SetArgV(int count, char **values) {187      struct string *list = calloc(count > 0 ? (size_t)count : 1, sizeof *list);188      if (!list) abort();189      for (int i = 0; i < count; i++) list[i] = (struct string){values[i], strlen(values[i])};190      arguments = list;191      argument_count = (size_t)count;192    }193    194    void KGEN_CompilerRT_GetArgV(struct argument_list *result) {195      result->list = arguments;196      result->count = argument_count;197    }198    199    /* The environment for a spawned process: Mojo cannot name a C variable,200     * and Dolly's dlsym, which upstream asks on Linux, belongs to programs that201     * load modules. Dolly's patch of the standard library calls this. */202    extern char **environ;203    char **KGEN_CompilerRT_Environ(void) { return environ; }204    205    /* The system */206    207    size_t KGEN_CompilerRT_NumPhysicalCores(void) { return 1; }208    size_t KGEN_CompilerRT_NumLogicalCores(void) { return 1; }209    size_t KGEN_CompilerRT_NumPerformanceCores(void) { return 1; }210    211    int KGEN_CompilerRT_fprintf(FILE *stream, const char *format, ...) {212      va_list values;213      va_start(values, format);214      int result = vfprintf(stream, format, values);215      va_end(values);216      return result;217    }218    219    /* A fault in Wasm is a trap: the process ends with status 126 and one line220     * from Dolly's supervisor. There is no signal to print a stack trace from. */221    void KGEN_CompilerRT_PrintStackTraceOnFault(void) {}222    223    /* As upstream answers while stack traces on errors are not configured. */224    int64_t KGEN_CompilerRT_GetStackTrace(char **text, int64_t depth) {225      (void)text;226      (void)depth;227      return 0;228    }229    230    void KGEN_CompilerRT_SetAsanAllocators(void) {}231    232    /* Tasks: one queue, run by whoever waits */233    234    struct task {235      struct task *next;236      void (*resume)(void *);237      void *handle;238    };239    240    struct chain {241      bool ready;242      struct task *waiting;243    };244    245    static struct task *first_task, **last_task = &first_task;246    static char device;247    static void *current_device;248    249    static void enqueue(struct task *task) {250      task->next = NULL;251      *last_task = task;252      last_task = &task->next;253    }254    255    static bool run_one(void) {256      struct task *task = first_task;257      if (!task) return false;258      first_task = task->next;259      if (!first_task) last_task = &first_task;260      void (*resume)(void *) = task->resume;261      void *handle = task->handle;262      free(task);263      resume(handle);264      return true;265    }266    267    static struct task *new_task(void (*resume)(void *), void *handle) {268      struct task *task = malloc(sizeof *task);269      if (!task) abort();270      *task = (struct task){NULL, resume, handle};271      return task;272    }273    274    void KGEN_CompilerRT_AsyncRT_InitializeChain(struct chain **chain) {275      *chain = calloc(1, sizeof **chain);276      if (!*chain) abort();277    }278    279    void KGEN_CompilerRT_AsyncRT_DestroyChain(struct chain **chain) {280      free(*chain);281      *chain = NULL;282    }283    284    void KGEN_CompilerRT_AsyncRT_Complete(struct chain **chain) {285      (*chain)->ready = true;286      while ((*chain)->waiting) {287        struct task *task = (*chain)->waiting;288        (*chain)->waiting = task->next;289        enqueue(task);290      }291    }292    293    void KGEN_CompilerRT_AsyncRT_Wait(struct chain **chain) {294      while (!(*chain)->ready) {295        if (!run_one()) {296          fputs("mojo: waiting for a task that nothing will complete\n", stderr);297          abort();298        }299      }300    }301    302    bool KGEN_CompilerRT_AsyncRT_Wait_Timeout(struct chain **chain, int64_t nanoseconds) {303      struct timespec start, now;304      clock_gettime(CLOCK_MONOTONIC, &start);305      while (!(*chain)->ready && run_one()) {306        clock_gettime(CLOCK_MONOTONIC, &now);307        if ((now.tv_sec - start.tv_sec) * 1000000000 + (now.tv_nsec - start.tv_nsec) >= nanoseconds) break;308      }309      return (*chain)->ready;310    }311    312    void KGEN_CompilerRT_AsyncRT_Execute(void (*resume)(void *), void *handle, int64_t worker) {313      (void)worker;314      enqueue(new_task(resume, handle));315    }316    317    void KGEN_CompilerRT_AsyncRT_AndThen(void (*resume)(void *), struct chain **chain, void *handle) {318      struct task *task = new_task(resume, handle);319      if ((*chain)->ready) {320        enqueue(task);321      } else {322        task->next = (*chain)->waiting;323        (*chain)->waiting = task;324      }325    }326    327    void *KGEN_CompilerRT_AsyncRT_GetOrCreateCPUDevice(void) { return current_device = &device; }328    void *KGEN_CompilerRT_AsyncRT_GetCurrentCPUDevice(void) { return current_device; }329    void KGEN_CompilerRT_AsyncRT_ReleaseCPUDevice(void *released) { (void)released; }330    uint32_t KGEN_CompilerRT_AsyncRT_ParallelismLevel(void) { return 1; }331    332    /* A spin waiter waits for another task: here, by running one. */333    void *KGEN_CompilerRT_AsyncRT_InitializeSpinWaiter(void) { return &device; }334    void KGEN_CompilerRT_AsyncRT_SpinWaiter_Wait(void *waiter) {335      (void)waiter;336      run_one();337    }338    void KGEN_CompilerRT_AsyncRT_DestroySpinWaiter(void *waiter) { (void)waiter; }339340FILE /tmp/mojo-runtime/gpu.c341    /* The device side of the Mojo package `gpu` (gpu.host.DeviceContext) over342     * Dolly's gpu@0. A kernel is WGSL as the compiler's webgpu target prints it:343     * pointer parameters are storage buffers bound in order, the other parameters344     * are 32-bit slots of one more buffer, block_dim is the workgroup size, and345     * what it prints are records of a last buffer, `log`. */346    #define _GNU_SOURCE347    #include <dolly/gpu.h>348    #include <errno.h>349    #include <stdint.h>350    #include <stdio.h>351    #include <stdlib.h>352    #include <string.h>353    354    enum { STORAGE = 128 | 4 | 8, READBACK = 1 | 8, CHUNK = 512 * 1024 };355    356    static dolly_gpu g;357    static uint64_t next = 1;358    359    static int64_t done(void) { return dolly_gpu_batch(&g) < 0 ? -errno : 0; }360    361    int64_t mojo_gpu_open(void) {362      if (g.scope) return 0;363      return dolly_gpu_open(&g, 0, 0) < 0 ? -errno : 0;364    }365    366    int64_t mojo_gpu_buffer(uint64_t bytes) {367      uint64_t id = next++;368      dolly_gpu_begin(&g);369      dolly_gpu_buffer(&g, id, (bytes + 3) & ~(uint64_t)3, STORAGE);370      int64_t error = done();371      return error ? error : (int64_t)id;372    }373    374    int64_t mojo_gpu_release(uint64_t id) {375      dolly_gpu_begin(&g);376      void *record = dolly_gpu_record(&g, DOLLY_GPU_RELEASE, 16);377      memcpy((char *)record + 8, &id, 8);378      return done();379    }380    381    int64_t mojo_gpu_write(uint64_t buffer, uint64_t offset, const void *data, uint64_t bytes) {382      for (uint64_t at = 0; at < bytes; at += CHUNK) {383        uint32_t size = bytes - at < CHUNK ? (uint32_t)(bytes - at) : CHUNK;384        /* WebGPU writes whole 32-bit words: the tail of a buffer of bytes is385         * padded with the zeros of the record, into the buffer's own padding. */386        uint32_t padded = (size + 3) & ~3u;387        dolly_gpu_begin(&g);388        char *record = dolly_gpu_record(&g, DOLLY_GPU_WRITE_BUFFER, 32 + padded);389        uint64_t target = offset + at;390        uint32_t data_offset = 32;391        memcpy(record + 8, &buffer, 8);392        memcpy(record + 16, &target, 8);393        memcpy(record + 24, &data_offset, 4);394        memcpy(record + 28, &padded, 4);395        memcpy(record + 32, (const char *)data + at, size);396        int64_t error = done();397        if (error) return error;398      }399      return 0;400    }401    402    /* gpu@0 maps at most 16 MiB at once and answers 64 KiB a call. */403    static int64_t read_mapped(uint64_t buffer, uint64_t offset, char *data, uint64_t bytes) {404      uint64_t staging = next++, size = (bytes + 3) & ~(uint64_t)3;405      dolly_gpu_begin(&g);406      dolly_gpu_buffer(&g, staging, size, READBACK);407      char *copy = dolly_gpu_record(&g, DOLLY_GPU_COPY_BUFFER, 48);408      memcpy(copy + 8, &buffer, 8);409      memcpy(copy + 16, &staging, 8);410      memcpy(copy + 24, &offset, 8);411      memcpy(copy + 40, &size, 8);412      dolly_gpu_submit(&g);413      dolly_gpu_map(&g, staging, size);414      int64_t error = done();415      for (uint64_t at = 0; !error && at < bytes; at += DOLLY_GPU_REPLY_BYTES) {416        uint64_t part = bytes - at < DOLLY_GPU_REPLY_BYTES ? bytes - at : DOLLY_GPU_REPLY_BYTES;417        if (dolly_gpu_read(&g, staging, at, part) != (int)part) error = errno ? -errno : -EIO;418        else memcpy(data + at, g.reply, part);419      }420      int64_t released = mojo_gpu_release(staging);421      return error ? error : released;422    }423    424    int64_t mojo_gpu_read(uint64_t buffer, uint64_t offset, void *data, uint64_t bytes) {425      enum { MAPPED = 16 * 1024 * 1024 };426      for (uint64_t at = 0; at < bytes; at += MAPPED) {427        int64_t error = read_mapped(buffer, offset + at, (char *)data + at, bytes - at < MAPPED ? bytes - at : MAPPED);428        if (error) return error;429      }430      return 0;431    }432    433    /* One shader module per kernel text and one pipeline per block_dim of it. */434    static struct { const char *code; uint64_t shader, pipeline; uint32_t block[3]; } kernels[64];435    436    static int64_t pipeline(const char *code, uint64_t bytes, const uint32_t block[3]) {437      uint64_t shader = 0;438      unsigned free = 64;439      for (unsigned i = 0; i < 64; i++) {440        if (!kernels[i].code) { if (free == 64) free = i; continue; }441        if (kernels[i].code != code) continue;442        shader = kernels[i].shader;443        if (!memcmp(kernels[i].block, block, sizeof kernels[i].block)) return (int64_t)kernels[i].pipeline;444      }445      if (free == 64) return -ENOSPC;446      dolly_gpu_begin(&g);447      if (!shader) {448        shader = next++;449        char *record = dolly_gpu_record(&g, DOLLY_GPU_CREATE_SHADER, 24 + bytes);450        uint32_t length = (uint32_t)bytes;451        memcpy(record + 8, &shader, 8);452        memcpy(record + 16, &length, 4);453        memcpy(record + 24, code, bytes);454      }455      /* COMPUTE_CONSTANTS: the entry "kernel", then block_x, block_y, block_z. */456      uint64_t id = next++;457      char *record = dolly_gpu_record(&g, DOLLY_GPU_COMPUTE_CONSTANTS, 40 + 3 * 24);458      uint32_t entry = 6, count = 3;459      memcpy(record + 8, &id, 8);460      memcpy(record + 16, &shader, 8);461      memcpy(record + 24, &entry, 4);462      memcpy(record + 28, &count, 4);463      memcpy(record + 32, "kernel", 6);464      for (unsigned axis = 0; axis < 3; axis++) {465        char *constant = record + 40 + axis * 24;466        uint32_t name = 7;467        double value = block[axis];468        memcpy(constant, &name, 4);469        memcpy(constant + 8, &value, 8);470        memcpy(constant + 16, "block_x", 7);471        constant[22] += (char)axis;472      }473      int64_t error = done();474      if (error) return error;475      kernels[free].code = code;476      kernels[free].shader = shader;477      kernels[free].pipeline = id;478      memcpy(kernels[free].block, block, sizeof kernels[free].block);479      return (int64_t)id;480    }481    482    /* A kernel's log: the words its records take, then each record's thread,483     * its length in bytes and its text. One that does not fit has no text. */484    enum { LOG_WORDS = 16 * 1024 };485    static const char log_binding[] = ") var<storage, read_write> log:";486    static uint64_t log_buffer;487    488    static int by_thread(const void *a, const void *b) {489      const uint32_t *left = *(const uint32_t *const *)a, *right = *(const uint32_t *const *)b;490      return left[0] != right[0] ? (left[0] < right[0] ? -1 : 1) : (left > right) - (left < right);491    }492    493    /* Writes what the kernel that has just run printed, thread after thread. */494    static int64_t print_log(void) {495      static uint32_t words[LOG_WORDS];496      static const uint32_t *records[LOG_WORDS / 2];497      int64_t error = mojo_gpu_read(log_buffer, 0, words, sizeof words);498      if (error) return error;499      unsigned count = 0, lost = 0;500      for (uint64_t at = 1; at <= words[0] && at + 2 <= LOG_WORDS; at += 2 + ((uint64_t)words[at + 1] + 3) / 4) {501        if (at + 2 + ((uint64_t)words[at + 1] + 3) / 4 <= LOG_WORDS) records[count++] = words + at;502        else lost = 1;503      }504      qsort(records, count, sizeof *records, by_thread);505      for (unsigned i = 0; i < count; i++) fwrite(records[i] + 2, 1, records[i][1], stdout);506      fflush(stdout);507      if (lost || words[0] > LOG_WORDS - 1) fputs("mojo: a kernel printed more than its 64 KiB of log\n", stderr);508      return 0;509    }510    511    int64_t mojo_gpu_launch(const char *code, uint64_t bytes, const uint32_t grid[3], const uint32_t block[3],512                            const uint64_t *buffers, const uint64_t *sizes, uint32_t buffer_count,513                            const uint32_t *slots, uint32_t slot_count) {514      int logs = memmem(code, bytes, log_binding, sizeof log_binding - 1) != 0;515      if (buffer_count + !!slot_count + logs > DOLLY_GPU_MAX_BINDINGS) return -E2BIG;516      int64_t compute = pipeline(code, bytes, block);517      if (compute < 0) return compute;518      uint64_t bound[DOLLY_GPU_MAX_BINDINGS], bound_sizes[DOLLY_GPU_MAX_BINDINGS], arguments = 0;519      memcpy(bound, buffers, buffer_count * 8);520      /* A kernel reads whole 32-bit words, also of a buffer of bytes. */521      for (unsigned i = 0; i < buffer_count; i++) bound_sizes[i] = (sizes[i] + 3) & ~(uint64_t)3;522      dolly_gpu_begin(&g);523      if (slot_count) {524        arguments = next++;525        dolly_gpu_buffer(&g, arguments, slot_count * 4, STORAGE);526        dolly_gpu_write(&g, arguments, slots, slot_count * 4);527        bound[buffer_count] = arguments;528        bound_sizes[buffer_count++] = slot_count * 4;529      }530      if (logs) {531        uint32_t empty = 0;532        if (!log_buffer) dolly_gpu_buffer(&g, log_buffer = next++, LOG_WORDS * 4, STORAGE);533        dolly_gpu_write(&g, log_buffer, &empty, 4);534        bound[buffer_count] = log_buffer;535        bound_sizes[buffer_count++] = LOG_WORDS * 4;536      }537      uint64_t group = next++;538      dolly_gpu_group(&g, group, (uint64_t)compute, buffer_count, bound, bound_sizes);539      char *record = dolly_gpu_record(&g, DOLLY_GPU_COMPUTE, 40);540      memcpy(record + 8, &compute, 8);541      memcpy(record + 16, &group, 8);542      memcpy(record + 24, grid, 12);543      dolly_gpu_submit(&g);544      int64_t error = done();545      if (!error && logs) error = print_log();546      if (error) return error;547      error = mojo_gpu_release(group);548      return error || !arguments ? error : mojo_gpu_release(arguments);549    }550    551    int64_t mojo_gpu_synchronize(void) { return dolly_gpu_wait(&g) < 0 ? -errno : 0; }552    553    /* What the device admits of a launch: the threads of a block, its x, y and554     * z, the workgroups of a dimension, the storage buffers of a kernel. */555    int64_t mojo_gpu_limits(uint32_t limits[6]) {556      static uint32_t known[6];557      if (!known[0]) {558        if (dolly_gpu_capabilities(&g) < 72) return errno ? -errno : -EIO;559        memcpy(known, g.reply + 44, 20);560        memcpy(known + 5, g.reply + 68, 4);561      }562      memcpy(limits, known, sizeof known);563      return 0;564    }565566SLOP cc -O2 -Wall -Wextra -Werror -c /tmp/mojo-runtime/runtime.c -o /tmp/mojo-runtime/runtime.o567SLOP cc -O2 -Wall -Wextra -Werror -c /tmp/mojo-runtime/gpu.c -o /tmp/mojo-runtime/gpu.o568SLOP ar rcs /usr/lib/libmojo-rt.a /tmp/mojo-runtime/runtime.o569SLOP ar rcs /usr/lib/libmojo-gpu.a /tmp/mojo-runtime/gpu.o570SLOP rm -rf /tmp/mojo /tmp/mojo.tar.gz /tmp/mojo-runtime571572# Where the compiler looks for packages, and what it hands cc to link a573# program: the runtime in place of upstream's shared CompilerRT, and the574# libraries of the package gpu, of which a link takes what the program uses.575# A program with a kernel needs gpu@0, which this package does not declare:576# it runs in an image that does.577FILE /etc/modular/modular.cfg578    [mojo-max]579    package_root = /usr580    import_path = /usr/lib/mojo581    compilerrt_path = /usr/lib/libmojo-rt.a582    shared_libs = -lmojo-gpu,-ldolly-gpu583EXPORTS TOOL mojo584EXPORTS LIB mojo-rt  /usr/lib/libmojo-rt.a585EXPORTS LIB mojo-gpu /usr/lib/libmojo-gpu.a586EXPORTS FOLDER mojo-packages /usr/lib/mojo587EXPORTS FOLDER mojo-licenses /usr/share/licenses/mojo588FILE /usr/share/man/cat1/mojo.1589    mojo: the Mojo compiler built inside Dolly, with its standard library, the590    package gpu and the cc package it links with.591    592      mojo build NAME.mojo && ./NAME     compiles a program for Dolly, then runs it593      mojo precompile -o P.mojoc DIR     makes a package; -I finds it594      mojo doc, mojo demangle            as upstream's595    596    mojo run and mojo NAME.mojo run a program through a JIT, which a WebAssembly597    process cannot do: they refuse and print the line above. mojo repl and mojo598    debug need LLDB, which is not built, and mojo format a formatter that is not599    installed. The only target is Dolly's (wasm64). A program that imports gpu600    runs where the image has gpu@0, as mojo-gpu does; elsewhere it is refused601    with status 126. The compiler runs on one thread.602