diff options
| author | Cliff Click <cliffc@acm.org> | 2026-08-12 11:20:14 -0700 |
|---|---|---|
| committer | Cliff Click <cliffc@acm.org> | 2026-08-12 11:20:14 -0700 |
| commit | 1cb8279a64f4f79615ac8358927b11237df29bf2 (patch) | |
| tree | d057b4ee723a9d29e16f2c71959c0ae2773cdb63 /chapter25/src/main | |
| parent | 628452bb9f6137d433395fae5a9f5d780fb0f1e1 (diff) | |
| download | Simple-1cb8279a64f4f79615ac8358927b11237df29bf2.tar.gz Simple-1cb8279a64f4f79615ac8358927b11237df29bf2.zip | |
Real argc/argv args
Beef up BubbleSort error handling
Diffstat (limited to 'chapter25/src/main')
6 files changed, 134 insertions, 30 deletions
diff --git a/chapter25/src/main/c/simple_crt.c b/chapter25/src/main/c/simple_crt.c new file mode 100644 index 00000000..b79e6f88 --- /dev/null +++ b/chapter25/src/main/c/simple_crt.c @@ -0,0 +1,57 @@ +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +// Native process arguments converted to ordinary Simple array layouts. +// The outer array is mutable; every contained byte array is published as +// immutable and non-null to Simple code. +typedef struct SimpleBytes { + uint32_t len; + unsigned char bytes[]; +} SimpleBytes; + +typedef struct SimpleArgs { + uint32_t len; + uint32_t pad; + SimpleBytes *args[]; +} SimpleArgs; + +__attribute__((CALL_CONV)) +extern int64_t simple_main(SimpleArgs *args); + +static SimpleArgs *simple_args(int argc, char **argv) { + if (argc < 0 || (uint64_t)argc > UINT32_MAX) + return NULL; + + SimpleArgs *args = calloc(1, sizeof(*args) + (size_t)argc * sizeof(args->args[0])); + if (args == NULL) + return NULL; + args->len = (uint32_t)argc; + + for (int i = 0; i < argc; i++) { + size_t len = strlen(argv[i]); + if (len > UINT32_MAX) { + free(args); + return NULL; + } + SimpleBytes *str = malloc(sizeof(*str) + len); + if (str == NULL) { + for (int j = 0; j < i; j++) free(args->args[j]); + free(args); + return NULL; + } + str->len = (uint32_t)len; + memcpy(str->bytes, argv[i], len); + args->args[i] = str; + } + return args; +} + +int main(int argc, char **argv) { + SimpleArgs *args = simple_args(argc, argv); + if (args == NULL) + return 1; + // The language-level hidden receiver is not part of the emitted ABI for + // free functions; source argument zero occupies native argument zero. + return (int)simple_main(args); +} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/Parser.java b/chapter25/src/main/java/com/seaofnodes/simple/Parser.java index 4c2dfc84..e964fa5b 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/Parser.java @@ -1450,8 +1450,10 @@ public class Parser { // Make an array type of t. Always record a mutable version, // but return the requested version. private TypeStruct typeAry( Type t, boolean efinal ) { - if( t instanceof TypeMemPtr tmp && tmp.notNull() ) - throw error("Arrays of reference types must always be nullable"); + // Non-null reference arrays are valid types, even though an ordinary + // zero-filled `new T[len]` cannot construct one. Allocation retains + // that check in alloc(); trusted runtimes (notably argv construction) + // may fill the private array completely before publishing it. String tname = ("[]"+t.str()).intern(); TypeStruct ta = (TypeStruct)TYPES.get(tname); if( ta==null ) { @@ -2406,22 +2408,33 @@ public class Parser { } if( _lexer.isEOF() ) throw error("Unclosed string"); - char esc = _lexer.nextChar(); - sb.append(switch( esc ) { - case 'n' -> '\n'; - case 't' -> '\t'; - case 'r' -> '\r'; - case '\\' -> '\\'; - case '"' -> '"'; - default -> throw error("Unknown string escape \\\\"+esc); - }); + sb.append(escapedChar("string")); } throw error("Unclosed string"); } + private char escapedChar(String kind) { + char esc = _lexer.nextChar(); + return switch( esc ) { + case 'n' -> '\n'; + case 't' -> '\t'; + case 'r' -> '\r'; + case '\\' -> '\\'; + case '"' -> '"'; + case '\'' -> '\''; + default -> throw error("Unknown "+kind+" escape \\\\"+esc); + }; + } + // Already parsed "'" private Node parseChar() { - return require(con(TypeInteger.constant(_lexer.nextChar())),"'"); + if( _lexer.isEOF() ) throw error("Unclosed character"); + char c = _lexer.nextChar(); + if( c=='\\' ) { + if( _lexer.isEOF() ) throw error("Unclosed character"); + c = escapedChar("character"); + } + return require(con(TypeInteger.constant(c)),"'"); } private boolean noLowerCase(String s) { diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java index 508cdcca..274be838 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java @@ -182,7 +182,7 @@ public class ElfWriter { // creates function and stores where it starts. // Filters to just this class.obj file. - void encodeFunctions(StopNode stop, Encoding enc, int text_idx) { + void encodeFunctions(StopNode stop, Encoding enc, int text_idx, boolean emitEntrySymbol) { for( Node stopcu : stop._inputs ) { for( Node ret : stopcu._inputs ) { FunNode fun = ((ReturnNode)ret).fun(); @@ -191,8 +191,15 @@ public class ElfWriter { int end = enc.opStart(fun.ret()) + enc.opLen(fun.ret()); long value = enc.opStart(fun); long size = end - value; - if( fun._name != null ) // Anonymous functions have no name - symbol(fun._name, text_idx, SYM_BIND_GLOBAL, SYM_TYPE_FUNC, value, size); + if( fun._name != null ) { // Anonymous functions have no name + // The C runtime owns `main` and adapts argc/argv into + // a normal Simple array. Keep the Simple function on + // its native ABI under a private entry symbol. + String name = emitEntrySymbol && "main".equals(fun._name) + ? "simple_main" + : fun._name; + symbol(name, text_idx, SYM_BIND_GLOBAL, SYM_TYPE_FUNC, value, size); + } } } } @@ -205,14 +212,14 @@ public class ElfWriter { for( FunNode fun : _code._linker ) if( fun != null && _code.owns(fun) ) { if( "main".equals(fun._name) ) - return; // Top-level declared a main, take the existing one + return; // encodeFunctions exported this as simple_main if( entryName.equals(fun._name) ) clinit = fun; // Keep top-level entry, if we don't find an explicit main } // No top-level main, so the top-level clinit becomes a default main if( clinit != null ) { assert enc.opStart(clinit) == 0; - symbol("main",text_idx, SYM_BIND_GLOBAL, SYM_TYPE_FUNC, enc.opStart(clinit), 0); + symbol("simple_main",text_idx, SYM_BIND_GLOBAL, SYM_TYPE_FUNC, enc.opStart(clinit), 0); return; } throw new IllegalStateException("Missing entry <clinit> "+entryName); @@ -293,7 +300,7 @@ public class ElfWriter { // populate function symbols - symbols.encodeFunctions(_code._stop, _code._encoding, text._index); + symbols.encodeFunctions(_code._stop, _code._encoding, text._index, emitEntrySymbol); // Export the source compilation unit <clinit> as the C runtime "main". // Encoding keeps this function at text offset zero. if( emitEntrySymbol ) diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java index 5d8ed03c..fa187000 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java @@ -63,6 +63,13 @@ public abstract class GlobalCodeMotion { private static void breakUpGlobalConstantSingle( Node con ) { + // If an immediate global-value user still spans functions, it cannot + // yet be assigned to one clone. Keeping the constant at Start is a + // valid schedule; a downstream value with concrete ownership can be + // split independently. + for( Node use : con.outs() ) + if( use != null && useFun(use)==null ) + return; // While constant has users in different functions while( true ) { // Find a function user, and another function @@ -106,26 +113,29 @@ public abstract class GlobalCodeMotion { } private static FunNode useFun(Node use) { + return useFun(use,new BitSet()); + } + + private static FunNode useFun(Node use, BitSet visit) { + if( visit.get(use._nid) ) return null; + visit.set(use._nid); if( use instanceof ReturnNode ret ) return ret.fun(); if( use instanceof ParmNode parm ) return parm.fun(); CFGNode cfg = use.cfg0(); - if( cfg==null ) return null; - FunNode fun = cfg.fun(); + FunNode fun = cfg==null ? null : cfg.fun(); // Use itself is not in a function... which makes it a 2-part // constant such as happens on some chips where large constants have // to be built up in parts. PtrToInt followed by the array-body Add is // another zero-code/global-value chain, so walk through any number of // single-use pieces until reaching the function-owned use. - if( fun==null ) { - do { - assert use.nOuts()==1; - use = use.out(0); - cfg = use.cfg0(); - fun = cfg==null ? null : cfg.fun(); - } while( fun==null ); - } + if( fun==null ) + for( Node out : use.outs() ) { + FunNode f = useFun(out,(BitSet)visit.clone()); + if( f != null && fun != null && f != fun ) return null; + if( f != null ) fun = f; + } return fun; } diff --git a/chapter25/src/main/smp/sys/Scan.smp b/chapter25/src/main/smp/sys/Scan.smp index acf366f7..acf9fbb8 100644 --- a/chapter25/src/main/smp/sys/Scan.smp +++ b/chapter25/src/main/smp/sys/Scan.smp @@ -3,6 +3,7 @@ // Scanner; a simple string parser. struct Scan { int !x; // Cursor + bool !bad; // True after a malformed token u8[~] buf; // Immutable byte array, must be set in 2nd constructor new Scan = { u8[~] b -> @@ -19,6 +20,7 @@ struct Scan { // True if find required character, and skip it; else False. val match = { u8 c -> skip(); + if( x >= buf# ) return false; if( buf[x] != c ) return false; x++; return true; @@ -28,8 +30,12 @@ struct Scan { val number = { -> skip(); bool neg = match('-'); + if( x >= buf# || buf[x] < '0' || '9' < buf[x] ) { + bad = true; + return 0; + } i64 n = 0; - while( '0' <= buf[x] & buf[x] <= '9' ) + while( x < buf# && '0' <= buf[x] & buf[x] <= '9' ) n = n*10 + (buf[x++] - '0'); return neg ? -n : n; }; diff --git a/chapter25/src/main/smp/sys/aryi64.smp b/chapter25/src/main/smp/sys/aryi64.smp index 8a154f34..c2bc394f 100644 --- a/chapter25/src/main/smp/sys/aryi64.smp +++ b/chapter25/src/main/smp/sys/aryi64.smp @@ -36,6 +36,14 @@ struct aryi64 { str.undo(2).add(']'); }; + // Comma-separated form used by command-line examples. + val csv = { -> + var str = new aryu8(new u8[1]); + for( int i=0; i<len; i++ ) + str.num(buf[i]).add(',').add(' '); + str.undo(2).add('\n'); + }; + val swap = { int i, int j -> i64 tmp = buf[i]; buf[i] = buf[j]; @@ -62,9 +70,12 @@ val parseAryI64 = { u8[~] str -> // Parse number commas "123, " until something else while( s.x < s.buf# ) { nums.add(s.number()); + if( s.bad ) return null; if( !s.match(',') ) break; }; // Close the list - return s.match(']') ? nums : null; + if( !s.match(']') ) return null; + s.skip(); + return s.x == s.buf# ? nums : null; }; |
