| // Copyright 2026 The IREE Authors | |
| // | |
| // Licensed under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // <stdio.h> for wasm32. | |
| // | |
| // IREE uses eyalroz/printf for all formatted output. The snprintf/vsnprintf | |
| // functions here are thin wrappers that defer to the eyalroz library via the | |
| // IREE printf API (iree_snprintf, etc.). The printf/fprintf functions that | |
| // write to streams use the iree_wasm_write syscall import. | |
| // | |
| // FILE is a minimal struct — just enough to carry a file descriptor. No | |
| // buffering, no seeking, no real filesystem. stdout and stderr write to the | |
| // JS console via the write import. stdin always returns EOF. | |
| #include <stdarg.h> | |
| #include <stdint.h> | |
| #include <stdio.h> | |
| #include <string.h> | |
| #include "syscall_imports.h" | |
| //===----------------------------------------------------------------------===// | |
| // FILE implementation | |
| //===----------------------------------------------------------------------===// | |
| struct _iree_wasm_file { | |
| int fd; | |
| int error; | |
| int eof; | |
| }; | |
| static struct _iree_wasm_file stdin_file = {0, 0, 1}; // Always EOF. | |
| static struct _iree_wasm_file stdout_file = {1, 0, 0}; | |
| static struct _iree_wasm_file stderr_file = {2, 0, 0}; | |
| FILE* stdin = &stdin_file; | |
| FILE* stdout = &stdout_file; | |
| FILE* stderr = &stderr_file; | |
| //===----------------------------------------------------------------------===// | |
| // Formatted output — delegated to IREE's printf API. | |
| // | |
| // IREE's printf.c provides iree_vsnprintf. The libc snprintf/vsnprintf below | |
| // are provided for code that calls the standard names directly. | |
| //===----------------------------------------------------------------------===// | |
| extern int iree_vsnprintf(char* buffer, size_t count, const char* format, | |
| va_list args); | |
| int snprintf(char* buffer, size_t count, const char* format, ...) { | |
| va_list args; | |
| va_start(args, format); | |
| int result = iree_vsnprintf(buffer, count, format, args); | |
| va_end(args); | |
| return result; | |
| } | |
| int vsnprintf(char* buffer, size_t count, const char* format, va_list args) { | |
| return iree_vsnprintf(buffer, count, format, args); | |
| } | |
| int sprintf(char* buffer, const char* format, ...) { | |
| va_list args; | |
| va_start(args, format); | |
| int result = iree_vsnprintf(buffer, (size_t)-1, format, args); | |
| va_end(args); | |
| return result; | |
| } | |
| int vsprintf(char* buffer, const char* format, va_list args) { | |
| return iree_vsnprintf(buffer, (size_t)-1, format, args); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // Stream output — writes to JS console via wasm import. | |
| //===----------------------------------------------------------------------===// | |
| // Formatting buffer for printf/fprintf. 4KB is generous for single format | |
| // calls. If a single printf produces more than this, the output is truncated. | |
| // IREE's own formatting always uses snprintf with explicit buffers, so this | |
| // limit only affects third-party code calling printf directly. | |
| #define PRINTF_BUFFER_SIZE 4096 | |
| int printf(const char* format, ...) { | |
| char buffer[PRINTF_BUFFER_SIZE]; | |
| va_list args; | |
| va_start(args, format); | |
| int length = iree_vsnprintf(buffer, sizeof(buffer), format, args); | |
| va_end(args); | |
| if (length > 0) { | |
| int write_length = | |
| length < (int)sizeof(buffer) ? length : (int)sizeof(buffer) - 1; | |
| iree_wasm_write(1, buffer, write_length); | |
| } | |
| return length; | |
| } | |
| int vprintf(const char* format, va_list args) { | |
| char buffer[PRINTF_BUFFER_SIZE]; | |
| int length = iree_vsnprintf(buffer, sizeof(buffer), format, args); | |
| if (length > 0) { | |
| int write_length = | |
| length < (int)sizeof(buffer) ? length : (int)sizeof(buffer) - 1; | |
| iree_wasm_write(1, buffer, write_length); | |
| } | |
| return length; | |
| } | |
| int fprintf(FILE* stream, const char* format, ...) { | |
| char buffer[PRINTF_BUFFER_SIZE]; | |
| va_list args; | |
| va_start(args, format); | |
| int length = iree_vsnprintf(buffer, sizeof(buffer), format, args); | |
| va_end(args); | |
| if (length > 0) { | |
| int write_length = | |
| length < (int)sizeof(buffer) ? length : (int)sizeof(buffer) - 1; | |
| iree_wasm_write(stream->fd, buffer, write_length); | |
| } | |
| return length; | |
| } | |
| int vfprintf(FILE* stream, const char* format, va_list args) { | |
| char buffer[PRINTF_BUFFER_SIZE]; | |
| int length = iree_vsnprintf(buffer, sizeof(buffer), format, args); | |
| if (length > 0) { | |
| int write_length = | |
| length < (int)sizeof(buffer) ? length : (int)sizeof(buffer) - 1; | |
| iree_wasm_write(stream->fd, buffer, write_length); | |
| } | |
| return length; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // Character and string I/O | |
| //===----------------------------------------------------------------------===// | |
| int fputc(int character, FILE* stream) { | |
| char c = (char)character; | |
| int32_t result = iree_wasm_write(stream->fd, &c, 1); | |
| return result == 1 ? (unsigned char)c : EOF; | |
| } | |
| int fputs(const char* string, FILE* stream) { | |
| size_t length = strlen(string); | |
| int32_t result = iree_wasm_write(stream->fd, string, (int32_t)length); | |
| return result >= 0 ? result : EOF; | |
| } | |
| int fgetc(FILE* stream) { | |
| (void)stream; | |
| return EOF; // No input on freestanding wasm. | |
| } | |
| int getchar(void) { return EOF; } | |
| int ungetc(int character, FILE* stream) { | |
| (void)character; | |
| (void)stream; | |
| return EOF; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // Block I/O | |
| //===----------------------------------------------------------------------===// | |
| size_t fread(void* buffer, size_t size, size_t count, FILE* stream) { | |
| (void)buffer; | |
| (void)size; | |
| (void)count; | |
| (void)stream; | |
| return 0; // No input on freestanding wasm. | |
| } | |
| size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream) { | |
| if (size == 0 || count == 0) return 0; | |
| size_t total = size * count; | |
| // Detect size*count overflow and int32_t truncation. The wasm import takes | |
| // int32_t, so we cannot write more than INT32_MAX bytes in a single call. | |
| if (total / size != count || total > (size_t)INT32_MAX) { | |
| stream->error = 1; | |
| return 0; | |
| } | |
| int32_t result = iree_wasm_write(stream->fd, buffer, (int32_t)total); | |
| if (result < 0) { | |
| stream->error = 1; | |
| return 0; | |
| } | |
| return (size_t)result / size; | |
| } | |
| int fflush(FILE* stream) { | |
| (void)stream; | |
| return 0; // No buffering. | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // File operations (stubs) | |
| //===----------------------------------------------------------------------===// | |
| FILE* fopen(const char* pathname, const char* mode) { | |
| (void)pathname; | |
| (void)mode; | |
| return NULL; | |
| } | |
| FILE* fdopen(int fd, const char* mode) { | |
| (void)fd; | |
| (void)mode; | |
| return NULL; | |
| } | |
| FILE* freopen(const char* pathname, const char* mode, FILE* stream) { | |
| (void)pathname; | |
| (void)mode; | |
| (void)stream; | |
| return NULL; | |
| } | |
| int fclose(FILE* stream) { | |
| (void)stream; | |
| return 0; | |
| } | |
| int fseek(FILE* stream, long offset, int whence) { | |
| (void)stream; | |
| (void)offset; | |
| (void)whence; | |
| return -1; | |
| } | |
| long ftell(FILE* stream) { | |
| (void)stream; | |
| return -1; | |
| } | |
| void rewind(FILE* stream) { (void)stream; } | |
| int feof(FILE* stream) { return stream->eof; } | |
| int ferror(FILE* stream) { return stream->error; } | |
| void clearerr(FILE* stream) { | |
| stream->error = 0; | |
| stream->eof = 0; | |
| } | |
| int fileno(FILE* stream) { return stream->fd; } | |
| int remove(const char* pathname) { | |
| (void)pathname; | |
| return -1; | |
| } | |
| int rename(const char* oldpath, const char* newpath) { | |
| (void)oldpath; | |
| (void)newpath; | |
| return -1; | |
| } |